sahin.io

All skills and agents

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-investors and lists are reserved segments.
  • Manifest (createSeoRouteManifest(), server/seoRouteManifest.ts): index route titled investorsIndexTitle(distinctPublicInvestorCount(...)); createCoInvestorRoutes(...) (server/coInvestorPages.ts) adds one route per investor reported in at least CO_INVESTOR_MINIMUM_COMPANIES (3) portfolio companies, grouped like buildReportedInvestors (server/publicDataEndpoints.ts); createListRoutes(...) (server/listPages.ts) adds /lists + every list that clears its entry threshold; /co-investors comes from LEGACY_MENU_PAGES (content/shared/navigation.yaml → shared/publicNavigation.ts); /angel-investor-rankings from SINGLE_PAGES in server/reactPageRoutes.ts.
  • Dispatch: renderRouteDocument() → renderPublicInvestorsIndexPage, renderCoInvestorPage, renderListRoute, renderLegacyFeaturePage, renderReactShellPage. reactPageForRoute() (server/publicReactPages.ts) maps coInvestor → firmView (in EMBED_SERVER_BODY_KINDS, server/publicReactDocument.ts) and /co-investors → coInvestors via buildCoInvestors.

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-list with 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, list title from lists.yaml via fit, listsIndexTitle, bestAngelsPageMeta, menu title in navigation.yaml; all fitted to 60 chars.
  • JSON-LD via buildPageGraph (server/publicSchema.ts): index CollectionPage + ItemList of directories; investor ProfilePage + FAQ + ItemList; lists CollectionPage with ItemList of Person + FAQPage; rankings add bestAngelsSchema (itemList, faqs, dateModified). All indexable; in sitemap and llms files (server/llmsText.ts). See seo-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 via server/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

  1. Check inputs validate: validateInvestorResearchAgainstPortfolio, loadAngelDirectory, angelRankings loader, vc-data key whitelist.
  2. Write the copy files listed above; run node scripts/sync-site-copy-module.mjs.
  3. Implement buildCoInvestorIndex → createCoInvestorRoutes → renderCoInvestorPage; then buildFirmViewProps + FirmViewPage.tsx (register per new-react-page, add to EMBED_SERVER_BODY_KINDS).
  4. Implement renderPublicInvestorsIndexPage with the lists callout and investor section.
  5. Implement buildListPagesModel → createListRoutes → renderListRoute.
  6. Keep /co-investors in navigation.yaml (see site-chrome-navigation) and its reactPageForRoute branch; keep the rankings entry in SINGLE_PAGES.
  7. Run deploy-gate → ui-before-after (index, one firm, /co-investors, rankings, one list; desktop + mobile) → ship-to-production.