new-react-page
The generic, file-by-file recipe for adding or rebuilding a React page on sahin.io/angel-investments — props builder, *Page.tsx, *Types.ts, CSS, the PublicReactPage union, PublicReactPageView, components.client/server, route registration (menu page, SINGLE_PAGES, FAMILIES or reactPageForRoute), YAML copy, titles, crumbs and tests. Use whenever a new page or slug family is added, an existing route is moved to React, or a page skill says "follow new-react-page".
- Source file
.claude/skills/new-react-page/SKILL.md
New React page
Every React page is a pair: a server props builder that computes all data (counts, links,
sourced values) from the render context, and a pure component that renders those props with
words from YAML. The static document (head, crumb, footer) is still built by the manifest; see
site-foundation for the render paths.
Worked examples in the repo
- Menu page:
/lessons—content/shared/navigation.yamlentry →legacyFeatureroute;reactPageForRoutereturns{ kind: 'lessons', props: buildLessonsProps(ctx) }by exact path. - Single page outside the menu:
/aboutinSINGLE_PAGES(server/reactPageRoutes.ts). - Slug family:
/status/{slug}inFAMILIES—listStatusDetailSlugs,statusDetailPageMeta,buildStatusDetailProps, crumb from the title.
File checklist (name = camelCase kind, e.g. statusDetail)
- Copy
content/pages/<page-name>.yaml(schemaVersion: 1, header comment listing placeholders; title/description candidates longest first). Runnode scripts/sync-site-copy-module.mjs→client/src/generated/copy/pages/<page-name>.ts. Rules:page-content-yaml. - Types
client/src/publicPages/<name>Types.ts—export interface <Name>PageProps; JSON-serialisable only (props are embedded in the HTML for hydration). Comment each field. - Props builder
server/publicPages/<name>.ts:build<Name>Props(ctx: RenderContext[, slug])— data fromctx.companies,ctx.facts,loadEnrichedRecords(),companyHref,publicCompanyLogo,sourced(); counts viasummarizePortfolioStatus(portfolio-counts-single-source).- Server-side words via
loadPageContent('<page-name>', SHAPE)withrecord/text/listfromserver/pageContent.ts. - For non-menu pages:
<name>PageMetareturning{ title, description }(title ≤ 60 chars, usefitPageTitle/firstFittingTitle; seeseo-head-and-schema). - Families:
list<Name>Slugs(ctx)and optional<name>Indexing(ctx, slug)→'noindex'for thin members.
- Component
client/src/publicPages/<Name>Page.tsx—export default function <Name>Page(props); importscopy from '@/generated/copy/pages/<page-name>'andfillText/fillParts/fillNodes/NUMBER_LOCALEfrom@/lib/siteCopy. No literal visible text, no fetch, no reading theme state at render (usetv()/ CSS variables;theme-system). One<h1>. - CSS
client/src/publicPages/styles/<name>.css, class-prefixed by the page; add@import "./publicPages/styles/<name>.css";toclient/src/index.css. - Union
client/src/publicPages/types.ts— import the props type and add| { kind: '<name>'; props: <Name>PageProps }toPublicReactPage. - View
client/src/publicPages/PublicReactPageView.tsx— import the props type, add<name>: ComponentType<<Name>PageProps>toPublicPageComponents, add thecase '<name>'. - Registries
components.server.ts(static import + entry inserverPageComponents) andcomponents.client.ts(<name>: lazy(() => import('./<Name>Page'))). Both must list it or the type check fails. - Route — pick one:
- Menu page: add to
pages+ one section incontent/shared/navigation.yaml, then addif (routePath === '/<path>') return { kind, props }inreactPageForRoute(server/publicReactPages.ts). Meta description: add toMENU_DESCRIPTION_FEATURESinserver/seoRouteManifest.tsif the menu description should be used. - Single page outside the menu: add
{ path, meta, page, lastModified?, schema? }toSINGLE_PAGESand the crumb tocontent/pages/page-crumbs.yaml. - Slug family: add
{ prefix, crumb, slugs, meta, page, indexing? }toFAMILIES. - Restyled existing kind (company, investors): branch on
routeKindinreactPageForRoute; add the kind toEMBED_SERVER_BODY_KINDSinserver/publicReactDocument.tsto keep the static body. URL and slug rules:public-url-conventions.
- Menu page: add to
- Structured data (optional) —
schema: ctx => ({ itemList, faqs, dateModified })on the spec. - Tests — extend the closest existing test: route presence/kind in
server/seo-route-manifest.test.ts; React document inserver/public-react-document.test.ts; title budget runs automatically inserver/keyword-[redacted].test.ts; copy policy inserver/content-in-yaml.test.tsandserver/site-copy-module.test.ts; links inserver/public-link-graph.test.ts; privacy inserver/private-deal-data.test.ts.
Rules
- No private deal data in props, YAML or CSS (
private-data-boundary); the one portfolio return figure only fromdata/portfolio-performance.yamlvia{irr}. - Company values only as sourced values (
SourcedValue,sourcedValueText). - Every count computed, never typed into YAML.
- The page must render with JavaScript off (static HTML is the SSR output) and hydrate without
mismatch: no
Date.now(), random,windoworlocalStorageduring render.
Finish
deploy-gate → ui-before-after (desktop and mobile, via the prefix proxy) → ship-to-production.