page-investors
The investor pages on sahin.io/angel-investments — the /angel-investors index, the per-firm and per-angel pages (/angel-investors/{investor}), the Co-Investor Network, the all-time angel rankings, and the sourced /lists pages. Use when building, rebuilding or changing any of them, their eligibility thresholds, wording, titles or schema.
- Source file
.claude/skills/page-investors/SKILL.md- Routes covered
/angel-investors/angel-investors/*/co-investors/angel-investor-rankings/lists/lists/*
Investor pages
Routes
| URL | Route kind | Count today | Render path |
|---|---|---|---|
| https://sahin.io/angel-investments/angel-investors | investorsIndex |
1 | static only (renderPublicInvestorsIndexPage) |
| https://sahin.io/angel-investments/angel-investors/{investor} | coInvestor |
99 | static body (renderCoInvestorPage) + React firmView on top |
| https://sahin.io/angel-investments/co-investors | legacyFeature |
1 | menu page shell + React coInvestors |
| https://sahin.io/angel-investments/angel-investor-rankings | reactPage |
1 | React bestAngels (SINGLE_PAGES entry) |
| https://sahin.io/angel-investments/lists | listsIndex |
1 | static only |
| https://sahin.io/angel-investments/lists/{list} | list (5) / cityList (1) |
6 | static only |
Today's lists: best-angel-investors, ai-angel-investors, software-angel-investors, fintech-angel-investors, hardware-angel-investors, angel-investors-in-san-francisco.
- Paths:
INVESTORS_INDEX_PATH,coInvestorPath(slug),LISTS_INDEX_PATH,listPath(slug)(shared/publicUrls.ts);angel-investorsandlistsare reserved segments. - Manifest (
createSeoRouteManifest(),server/seoRouteManifest.ts): index route titledinvestorsIndexTitle(distinctPublicInvestorCount(...));createCoInvestorRoutes(...)(server/coInvestorPages.ts) adds one route per investor reported in at leastCO_INVESTOR_MINIMUM_COMPANIES(3) portfolio companies, grouped likebuildReportedInvestors(server/publicDataEndpoints.ts);createListRoutes(...)(server/listPages.ts) adds/lists+ every list that clears its entry threshold;/co-investorscomes fromLEGACY_MENU_PAGES(content/shared/navigation.yaml→shared/publicNavigation.ts);/angel-investor-rankingsfromSINGLE_PAGESinserver/reactPageRoutes.ts. - Dispatch:
renderRouteDocument()→renderPublicInvestorsIndexPage,renderCoInvestorPage,renderListRoute,renderLegacyFeaturePage,renderReactShellPage.reactPageForRoute()(server/publicReactPages.ts) mapscoInvestor→firmView(inEMBED_SERVER_BODY_KINDS,server/publicReactDocument.ts) and/co-investors→coInvestorsviabuildCoInvestors.
Files
| Page | Server | Client | Copy |
|---|---|---|---|
| Index | renderPublicInvestorsIndexPage, investorCollections (server/seoRouteManifest.ts), renderCoInvestorIndexSection (server/coInvestorPages.ts), renderListsCallout (server/listPages.ts) |
— | content/pages/investor-directories.yaml (index block) |
| Investor | server/coInvestorPages.ts (buildCoInvestorIndex, coInvestorTitle, coInvestorDescription, relatedCoInvestors, vcPublicProfile whitelist), server/publicPages/firmView.ts |
FirmViewPage.tsx, firmViewTypes.ts, styles/firmView.css |
co-investor.yaml, firm-view.yaml |
| Co-Investor Network | buildCoInvestors (server/publicReactPages.ts), renderLegacyFeaturePage |
CoInvestorsPage.tsx (props type CoInvestorsPageProps in types.ts; no own stylesheet) |
co-investors.yaml, menu-pages.yaml |
| Rankings | server/publicPages/bestAngels.ts (bestAngelsPageMeta, buildBestAngelsProps, bestAngelsSchema, bestAngelsLastModified), server/angelRankings.ts |
BestAngelsPage.tsx, bestAngelsTypes.ts, styles/bestAngels.css |
angel-investor-rankings.yaml |
| Lists | server/listPages.ts (buildListPagesModel, renderListPage, renderListsIndexPage), server/angelDirectory.ts |
— | lists.yaml |
All copy files live in content/pages/. React components are registered in client/src/publicPages/types.ts, components.client.ts, components.server.ts; CSS imported from client/src/index.css. Static CSS: PUBLIC_PAGE_CSS (server/publicChrome.ts) plus LIST_CSS in server/listPages.ts.
Data: data/public-investor-research.json (verified records, sources), client/public/vc-data.json (only RENDERED_VC_PROFILE_KEYS: name, philosophy, focus, headquarters, founded, website; any unknown key fails the build), data/angel-directory.json (lists), data/angel-rankings.json (rankings), data/public-company-valuations.json (sourced outcomes), client/public/portfolio-data.json.
Sections on the page
- /angel-investors: header (computed description + note) → by Category chips → by Sub-Category chips → lists callout → by investor chips → by company (
#investor-company-listwith filter controls, links to /{company}/angel-investors) → related → back to portfolio. - /angel-investors/{investor}: React firm view (header, stat cards, Overview / By Stage / Shared / Patterns / Deep Analysis tabs; fields with no data are omitted). Static body: header + answer → context → optional public profile block → shared portfolio companies → Categories → sources (opaque
/sources/{id}links, publisher as anchor) → related investors → FAQ → footer. - /co-investors: React network page (firms, shared companies, network graph) linking to
/angel-investors/{investor}when that route exists. - /angel-investor-rankings: author box, method, one profile card per ranked person with each counted outcome and its source, comparison table, FAQ, further links.
- /lists/{list}: header → numbered entries → most-active tiers (top list only) → methodology → companies → reach → FAQ → more lists. /lists: header → list cards with counts → about → more lists.
SEO
- Titles:
investorsIndexTitle,coInvestorTitle, listtitlefromlists.yamlviafit,listsIndexTitle,bestAngelsPageMeta, menu title innavigation.yaml; all fitted to 60 chars. - JSON-LD via
buildPageGraph(server/publicSchema.ts): indexCollectionPage+ItemListof directories; investorProfilePage+ FAQ +ItemList; listsCollectionPagewithItemListof Person +FAQPage; rankings addbestAngelsSchema(itemList, faqs, dateModified). All indexable; in sitemap and llms files (server/llmsText.ts). Seeseo-head-and-schema.
Rules
- Wording: a person or firm is "a investor in N companies". Never "co-invested", "led", "alongside", rounds, amounts or dates; the owner never gets an investor page and is left out of names and counts —
private-data-boundary. - Rankings: the owner is a ranked entry by the same method, never first on his own list; an outcome counts only with a public source.
assertPublicText/findRestrictedTerms(server/publicContentPolicy.ts) guard rendered text; a whitelisted vc-data text with a restricted term is omitted.- Counts computed —
portfolio-counts-single-source. Slugs —public-url-conventions. Words in YAML —page-content-yaml. Old URLs (e.g. merged most-active list) 301 viaserver/legacyRedirects.ts.
Tests
server/co-investor-pages.test.ts, server/list-pages.test.ts, server/angel-rankings.test.ts, server/seo-route-manifest.test.ts, server/public-schema.test.ts, server/public-react-document.test.ts, server/public-page-recovery.test.ts, server/public-routing.test.ts, server/public-company-value.test.ts, server/keyword-[redacted].test.ts, server/llms-text.test.ts, server/legacy-redirects.test.ts, server/content-in-yaml.test.ts, server/seo.test.ts.
Rebuild from scratch
- Check inputs validate:
validateInvestorResearchAgainstPortfolio,loadAngelDirectory,angelRankingsloader, vc-data key whitelist. - Write the copy files listed above; run
node scripts/sync-site-copy-module.mjs. - Implement
buildCoInvestorIndex→createCoInvestorRoutes→renderCoInvestorPage; thenbuildFirmViewProps+FirmViewPage.tsx(register pernew-react-page, add toEMBED_SERVER_BODY_KINDS). - Implement
renderPublicInvestorsIndexPagewith the lists callout and investor section. - Implement
buildListPagesModel→createListRoutes→renderListRoute. - Keep
/co-investorsinnavigation.yaml(seesite-chrome-navigation) and itsreactPageForRoutebranch; keep the rankings entry inSINGLE_PAGES. - Run
deploy-gate→ui-before-after(index, one firm, /co-investors, rankings, one list; desktop + mobile) →ship-to-production.