sahin.io

All skills and agents

site-foundation

The base of sahin.io/angel-investments — the stack, the route manifest, the two render paths (static renderers vs React pages), the static build, and the order in which the page skills rebuild the site. Use first when rebuilding the site from scratch, before adding a new route family, or when orienting in how a URL becomes an HTML file.

Source file
.claude/skills/site-foundation/SKILL.md

Site foundation

What it is

A public, statically generated portfolio site served at https://sahin.io/angel-investments/ (Cloudflare Pages behind the sahin.io proxy; see production-deployment). Every public URL is a route in one manifest; every route is pre-rendered to HTML at build time and then hydrated by a React client bundle.

Stack (package.json): React 19, Vite 7, Tailwind 4, wouter (client routing), TypeScript 5.9, vitest, tsx. Copy lives in YAML/Markdown under content/ (see page-content-yaml).

Files

  • server/seoRouteManifest.ts — createSeoRouteManifest() builds every route ({ path, canonicalUrl, file, kind, title, lastModified, indexing }); renderRouteDocument() dispatches by kind to the static renderers; renderRouteHtml() wraps it and refuses a noindex route whose HTML lacks the robots meta.
  • server/publicPortfolio.ts — RouteKind union: home, explore, legacyFeature, reactPage, newsDetail, company, category, subcategory, companyInvestors, categoryInvestors, subcategoryInvestors, investorsIndex, coInvestor, guidesIndex, guide, about, thesis, pitch, faq, glossary, list, cityList, listsIndex.
  • server/reactPageRoutes.ts — React-only routes: SINGLE_PAGES and slug FAMILIES (/founder, /status, /news, /sectors, /market); listed in the manifest as reactPage.
  • server/publicReactPages.ts — reactPageForRoute(path, ctx) returns { kind, props } for any route that has a React page (SINGLE_PAGES/FAMILIES first, then route kinds and exact paths).
  • server/publicPages/*.ts — one props builder per React page (build<Name>Props, <name>PageMeta).
  • client/src/publicPages/ — *Page.tsx, *Types.ts, types.ts (PublicReactPage union), PublicReactPageView.tsx, components.server.ts, components.client.ts, styles/*.css.
  • server/publicReactDocument.ts — renderPublicReactDocument() merges static head + React body.
  • client/src/ssr/entry-server.tsx — renderPublicPage(), built by vite.config.ssr.ts.
  • client/src/App.tsx, client/src/main.tsx — the same tree on server and client (hydration).
  • scripts/generate-static.ts — the static build.

Two render paths

  1. Static renderer. renderRouteDocument() → renderPublic*Page / renderEditorialDocument / renderListRoute / renderLegacyFeaturePage / renderReactShellPage, each ending in renderDocument(ctx, headSpec(...), breadcrumbs, body). This HTML always exists: it owns the <head> (title, canonical, OG, JSON-LD — see seo-head-and-schema) and the crawlable body.
  2. React page. If reactPageForRoute() returns a page, renderPublicReactDocument() keeps the static head, replaces the static header with the server-rendered React layout (App → NavBar + PublicReactPageView), and passes the static breadcrumb and footer in as ServerChrome { crumbHtml, footerHtml, articleHtml? }. Kinds in EMBED_SERVER_BODY_KINDS (companyView, companyInvestorsView, categoryInvestorsView, firmView) also keep the static body below the React part; editorial routes keep their <article class="editorial">. With no React page, the static body is wrapped in ServerPageBody (.public-page).

reactPage routes have only a shell static body (renderReactShellPage: H1 = crumb, intro = description); the React page replaces it. Menu pages (legacyFeature) come from content/shared/navigation.yaml (see site-chrome-navigation).

The hub (/, kind home) is not rendered by the dispatcher: renderPublicHubDocument() fills the Vite shell's <!--PUBLIC_HEAD--> / <!--PUBLIC_INDEX--> markers (see page-hub-home).

The build (scripts/generate-static.ts)

Run after npx vite build --config vite.config.cloudflare.ts (CI does both; see .github/workflows/deploy-cloudflare.yml):

  1. Vite SSR build of client/src/ssr/entry-server.tsx → dist/ssr-entry/.
  2. Read dist/cloudflare/index.html (must still have both markers); write the themed CSS asset (buildThemedCssAsset); copy the allowlisted public assets (favicon, robots.txt, images).
  3. Load portfolio, investor research, editorial (status: approved only), taxonomy content; createSeoRouteManifest(); createRenderContext() (one shared facts).
  4. For each non-home route: renderRouteHtml → assertPublicHtml → raw-source-URL checks → renderPublicReactDocument → every NAVIGATION_HREFS link must be present → write dist/cloudflare/ssr/<route.file>.
  5. Hub index.html, seo-route-manifest.json, source-reference-map.json, sitemap.xml (buildCanonicalSitemap), legacy-redirect-map.json, llms.txt, llms-full.txt.

Local: npx vite build --config vite.config.cloudflare.ts && NODE_ENV=development npx tsx scripts/generate-static.ts. Local screenshots need the prefix proxy (see ui-before-after).

Rebuild order (all skills)

  1. site-foundation (this) — manifest, dispatcher, render paths, build.
  2. site-chrome-navigation — header, sidebar, mobile tabs, footer, breadcrumbs, menu YAML.
  3. seo-head-and-schema — head tags, titles, canonical, JSON-LD, sitemap, llms, robots.
  4. theme-system — investor themes, CSS variables, hub views.
  5. i18n-locales — the English-only locale setup.
  6. new-react-page — the generic recipe every React page follows.
  7. Page families: page-hub-home → page-company → page-taxonomy → page-investors → page-vc-portfolios → page-editorial → page-angel-guides-reports → page-news → page-founders → page-status-sectors-markets → page-performance → page-research-tools → page-skills-and-agents.
  8. Every change: deploy-gate → ui-before-after → ship-to-production.

Cross-cutting rules: page-content-yaml (no copy in code), portfolio-counts-single-source, private-data-boundary, public-url-conventions, project-architecture.

Tests

server/seo-route-manifest.test.ts (route kinds, reactPage routes), server/public-routing.test.ts, server/public-react-document.test.ts, server/route-indexing.test.ts, server/public-site.test.ts, server/content-in-yaml.test.ts, server/production-startup.test.ts. Run them all through deploy-gate (scripts/gate.ts).