page-taxonomy
The Category and Sub-Category collection pages and their investor directories on sahin.io/angel-investments (/{category}, /{category}/{sub}, and each one's /angel-investors directory). Use when building, rebuilding or changing these pages, their editorial Markdown, their noindex rules, titles or schema, or when a new Category or Sub-Category appears.
- Source file
.claude/skills/page-taxonomy/SKILL.md- Routes covered
kind:categorykind:subcategorykind:categoryInvestorskind:subcategoryInvestors
Category and Sub-Category pages
Routes
| URL | Route kind | Count today | Render path |
|---|---|---|---|
| https://sahin.io/angel-investments/{category} (e.g. https://sahin.io/angel-investments/ai) | category |
14 | static only |
| https://sahin.io/angel-investments/{category}/{sub} | subcategory |
69 | static only |
| https://sahin.io/angel-investments/{category}/angel-investors | categoryInvestors |
14 | static body + React categoryInvestorsView on top |
| https://sahin.io/angel-investments/{category}/{sub}/angel-investors | subcategoryInvestors |
54 (5 noindex) |
static body + React categoryInvestorsView on top |
- Paths:
taxonomyPath(kind, slug)/taxonomyInvestorsPath(kind, slug)(shared/publicUrls.ts). A Sub-Category's parent comes only fromdata/subcategory-parents.json(subcategoryParentthrows when missing). - Manifest (
createSeoRouteManifest(),server/seoRouteManifest.ts): for each term fromtaxonomyTerms(portfolio.companies, kind)push the collection route (title from approved Markdown ortaxonomyPageTitle(label),lastModifiedfromlastReviewed); push the investor route only whencollectionHasEligibleInvestors(...)(at leastCOLLECTION_INVESTOR_MINIMUM= 3 companies with verified research), titled bysectorInvestorDirectory(...).title, andnoindexwhen its key is induplicateInvestorDirectoryKeys(...)(server/sectorInvestorDirectory.ts). - An approved Markdown file whose slug matches no live term fails the build.
- Dispatch:
renderPublicTaxonomyPageandrenderPublicInvestorCollectionPage;reactPageForRoute()maps both investor kinds tocategoryInvestorsView, which is inEMBED_SERVER_BODY_KINDS(server/publicReactDocument.ts; the staticsector-angelssection is dropped because the React view shows angels first).
Files
- Renderers/helpers:
renderPublicTaxonomyPage,taxonomyInvestorCoverage,renderTaxonomyInvestorCoverage,renderTaxonomySources,renderTaxonomyFaqs,taxonomyCrumbs,renderPublicInvestorCollectionPage,renderSectorAngels,investorCollections(server/seoRouteManifest.ts). - Directory model:
sectorInvestorDirectory,sectorDirectoryTitle,sectorListPath,duplicateInvestorDirectoryKeys,investorDirectoryCompanySets(server/sectorInvestorDirectory.ts). - Editorial Markdown:
content/taxonomy/{slug}.md(todayai,climate,developer-tools,fintech,robotics), loaded byloadTaxonomyContent/findTaxonomyContent(server/taxonomyContent.ts); frontmatterslug, kind, title, description, h1, targetQueries, faqs, sources, lastReviewed, status; body 600-1200 words, exactly 3 FAQs, title ≤ 60, description ≤ 155. Icons:content/taxonomy/icons.yaml. - Copy:
content/pages/taxonomy-page.yaml(taxonomyCopy()),content/pages/investor-directories.yaml(investorCopy().collection),content/pages/category-investors.yaml(React view). - React:
server/publicPages/categoryInvestorsView.ts(buildCategoryInvestorsViewProps),client/src/publicPages/CategoryInvestorsViewPage.tsx+categoryInvestorsViewTypes.ts, CSSclient/src/publicPages/styles/categoryInvestorsView.css. Static CSS:.taxonomy-*,.sector-*,.prose,.faq,.sourcesinPUBLIC_PAGE_CSS(server/publicChrome.ts). - Data:
client/public/portfolio-data.json(category/subcategoryarrays),data/public-investor-research.json,data/subcategory-parents.json,data/public-company-valuations.json.
Sections on the page
Collection page — with approved Markdown: <article class="taxonomy-editorial"> header (eyebrow, h1, updated date, answer-first intro, collection size, investor-directory link) → prose (internal links resolved against the manifest; FAQ section placed at the FAQ slot or appended) → investor coverage panel (top 8 investors across the term's companies, linked to firm pages) → sources. Without Markdown: only a header from taxonomy-page.yaml (eyebrow, h1, description, investor link). Then, in both cases: companies grid with list controls (#taxonomy-company-list, sortByMarketCap) → related pages → footer link.
Investor directory — React view: "Who invests in {X} startups?" (when a sourced list owns "{X} angel investors") or "{X} angel investors and VCs", angels, companies ranked by company value. Static body below: header links (best list when it exists, all startups in the term, all investors) → company cards linking to each /{company}/angel-investors → related pages → back link.
SEO
- Collection: title/description/h1 from Markdown frontmatter, else
taxonomyPageTitle(label)andtaxonomy-page.yaml; JSON-LDCollectionPage+ItemListof companies +FAQPagewhen FAQs exist +dateModifiedfromlastReviewed. - Directory: title/description from
sectorInvestorDirectory;CollectionPage+ItemListof company investor pages; breadcrumbs hub → Category (→ Sub-Category) → "Angel investors". - A
noindexroute still renders butrenderRouteHtmlthrows unless the robots meta is present; it is left out of the sitemap, llms files and IndexNow (isIndexableRoute). Details:seo-head-and-schema.
Rules
- Editorial gate: only
status: approvedMarkdown is loaded; drafts never publish (preview withpnpm content:preview). - A new Category/Sub-Category from a sync needs a slug and a parent pin first —
public-url-conventions. - Counts ("N companies") computed, never typed —
portfolio-counts-single-source. Words in YAML/MD —page-content-yaml. - Directory wording: "investor"; never "co-invested", "led", rounds, amounts, dates; the owner is left out of names and counts —
private-data-boundary.
Tests
server/taxonomy-content.test.ts, server/sector-investor-directories.test.ts, server/route-indexing.test.ts, server/seo-route-manifest.test.ts, server/public-schema.test.ts, server/public-urls.test.ts, server/public-investor-research.test.ts, server/keyword-[redacted].test.ts, server/llms-text.test.ts, server/legacy-redirects.test.ts, server/seo-watchlist.test.ts.
Rebuild from scratch
- Verify every Sub-Category has a parent in
data/subcategory-parents.json. - Keep the taxonomy loop in
createSeoRouteManifest()including the eligibility check andduplicateInvestorDirectoryKeysnoindex. - Write
taxonomy-page.yaml,investor-directories.yaml(collectionblock),category-investors.yaml; runnode scripts/sync-site-copy-module.mjs. - Implement
renderPublicTaxonomyPage(editorial and fallback headers) andrenderPublicInvestorCollectionPageon top ofsectorInvestorDirectory. - Implement
buildCategoryInvestorsViewProps+CategoryInvestorsViewPage.tsx, register pernew-react-page, add the kind toEMBED_SERVER_BODY_KINDS. - Add or edit
content/taxonomy/{slug}.mddrafts; setstatus: approvedonly after owner review. - Run
deploy-gate→ui-before-after(a Category, a Sub-Category, one directory; desktop + mobile) →ship-to-production.