Fix site indexability with per-route prerendering
Every URL was served one index.html whose canonical was hardcoded to /, so /usage and /playground declared themselves duplicates of the homepage. The body was an empty div, and there was no robots.txt or sitemap.
Each route is now prerendered to its own HTML file with its own head and rendered markup.
src/lib/seo.tslists every page;src/lib/head.tsdescribes the head once, used at build time and on client navigationbun run buildadds an SSR build plusscripts/prerender.ts, writing one file per route andsitemap.xmlrobots.txt, JSON-LD on the homepage,noindexon the 404- the catch-all rewrite is gone, so unknown URLs return a real 404 instead of the homepage at 200
Adding a route now requires adding it to PAGES, or it 404s on a direct hit.
Also adds CLAUDE.md, and moves the // comment out of overrides — npm reads every key there as a package name and refuses to run on it.
Summary by cubic
Prerenders each route to its own HTML and head so crawlers can index pages. Old: one index.html for every URL with canonical “/” and an empty body; New: each route serves rendered markup, its own canonical/title/OG/Twitter, emits a sitemap, and unknown URLs return a real 404 with noindex.
- Build:
apps/webrunsvite build,vite build --ssr src/entry-server.tsx --outDir dist-ssr, thenbun scripts/prerender.ts; outputsdistanddist-ssr..gitignore,turbo.json,tsconfig.json, andknip.jsonupdated. - Head/SEO:
src/lib/seo.tsdefinesPAGES;src/lib/head.tscentralizes tags and the client applies them on SPA navigation. JSON‑LD on the homepage;robots.txtadded; 404 servesnoindex. - Deploy: Vercel catch‑all rewrite removed;
cleanUrls: true,trailingSlash: false. Rootvercel.jsonis the source of truth. - Required: When adding a route, add it to
PAGESinapps/web/src/lib/seo.ts, or direct hits 404 and the page is excluded from the sitemap. - QA: Verify
/robots.txtand/sitemap.xmlload,/usageand/playgroundhave correct canonicals/titles, and unknown paths return a 404 withnoindex. - Tooling: Moved the comment out of
overridesintooverridesCommentin the rootpackage.jsonso npm can read the workspace.