ADR-0008 Next.js for the docs site
#Context
Playbook §11's ninety-second demo is the system's core requirement for the docs site, and it is stated as a running scenario, not a description of one: "A live screen renders, calm. One element escalates to level 4 ... CI fails. The failure message names both elements, the scope, and the rule. The receipt appears on the docs site." §11 is explicit about what that means for every decision downstream of it: "If a piece of work does not make that ninety seconds sharper, it waits. The failing check is the portfolio; the component gallery is scenery." The docs site has to run the system's real components live in the browser — the receipt has to *appear*, not be paraphrased — because the failing check is the artifact being judged, not a screenshot of one.
This ADR's number was reserved from Phase 0 rather than assigned in sequence: the register has carried "0008 — Docs site stack — Open" since the earliest ADRs, blocking Phase 6, while every other slot filled in order. It stays open until Phase 6 actually needs the site; deciding it now, as Phase 5 closes, is deciding it before it blocks anything, which is when a decision this load-bearing is cheapest to get right.
#Decision
The docs site (apps/docs, serving r136.dev) is a Next.js application. It shares the same React and TypeScript stack already used across packages/react and apps/storybook, which means the docs site imports the real component package directly — @wolf-rayet/react's actual Alert, StatusIndicator and the rest, the same build every Storybook story and every fixture already renders — rather than reimplementing components a second time in a documentation-specific framework. It deploys to the same host already serving Storybook (apps/storybook/vercel.json), so the docs site's own deploy is a second Vercel project on infrastructure this repo has already proven out, not a new one to stand up.
#Rejected options
A documentation-focused static site generator (Docusaurus, Nextra-as-pure-docs, VitePress) — faster to stand up prose pages and an API reference table. Rejected because none of them has a natural way to embed the actual live components without rebuilding them a second time outside their own framework — which is exactly the duplication this system has refused everywhere else in the repository: one token tier, one layer model, one tone-metadata mechanism. A docs site that reimplemented Alert in MDX to get it onto a prose page would be a second Alert, and the ninety-second demo would be running that one, not the one CI actually gates.
A lighter content-first framework (Astro, plain Vite + a router) — better raw performance on text-heavy pages, since most of a docs site's weight is prose rather than interactivity. Rejected because it would introduce a second component model into an otherwise entirely React monorepo for no gain proportional to the complexity: every other app and package here is React, and a docs site built in a different framework would need its own island or bridge just to mount the real components §11 requires, paying integration cost to save a performance margin nothing in this system's stated priorities asks for.
A fully custom build with no framework — maximum control over the output, no framework opinions to work around. Rejected because it spends effort re-solving routing, bundling and build tooling instead of spending it on the demo and the receipts, which is what §12 says reviewers actually look for ("evidence of judgment and operational discipline," not a hand-rolled router). Framework selection is not itself the differentiator this project is building toward; the receipts are.
#Consequences
apps/docs becomes a Next.js app inside the existing pnpm workspace, matching the layout §13 already specifies (apps/docs/ reserved, serving r136.dev). It consumes packages/react and packages/core as workspace dependencies — the same workspace:* pattern every other consuming package already uses — and reads the generated receipts (packages/eslint-plugin/receipts/, the tokens and budget engine's own committed output) as its data source for published passing and must-fail checks, rather than a second, hand-maintained copy of numbers CI already produces.
What this makes easier: the ninety-second demo runs as a real page, not a recording of one, because the component rendering it is the component the budget engine measures. Landing a new component or a new receipt is a docs site update of "consume the new export," not "author a new specimen in a second framework."
What this makes harder: the docs site now carries a real build step and a real dependency on the monorepo's own packages resolving correctly, so a break in packages/react's build is a break in the docs site's build too — which is the coupling this decision is *for*, not a cost incurred by accident.
Enforcement: none yet. This ADR unblocks Phase 6 (docs site); it adds no row to playbook §10's enforcement table on its own; the first Phase 6 unit that ships a docs.yml CI workflow is what does.