From b6c3fd9a28c09601a33a0c0b0f287f20c7cf3a6f Mon Sep 17 00:00:00 2001 From: implementer Date: Mon, 31 Aug 2026 00:51:22 +0000 Subject: [PATCH] docs(adr): record React SSR for public rendering decision as ADR-007 --- .../adr/ADR-007-react-ssr-public-rendering.md | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 docs/adr/ADR-007-react-ssr-public-rendering.md diff --git a/docs/adr/ADR-007-react-ssr-public-rendering.md b/docs/adr/ADR-007-react-ssr-public-rendering.md new file mode 100644 index 0000000..0708356 --- /dev/null +++ b/docs/adr/ADR-007-react-ssr-public-rendering.md @@ -0,0 +1,112 @@ +# ADR-007: React SSR for public rendering + +- Status: Accepted +- Date: 2026-08-31 +- Deciders: platform stream +- References: ADR index (section 70), ADR-001, Architecture wiki (sections 1, 19, 42), + Technology-Stack wiki (sections 5.2, 6, 7, 8) + +## Context + +EPPP is a modular monolith (ADR-001) whose single application image contains the +public server, admin API, rendering pipeline, extension runtime and core services. +The public rendering pipeline is already documented: Fastify route → SiteResolver → +VisitorPreferenceResolver → ContentService → PageComposition + BlockRegistry → +ThemeResolver → React DOM server renderer → HTML (Architecture wiki §19), and the +v0.1 public surface exposes `GET /`, `GET /posts/:slug`, `GET /assets/*`, +`GET /media/*`, `GET /health/live`, `GET /health/ready` and `GET /admin/*` (§42). +This ADR is the focused record of the rendering choice at the end of that pipeline. + +React is already the pinned rendering technology in the golden compatibility tuple: +React / React DOM 19.2.8, class C, in the runtime stack and golden tuple +(Technology-Stack §5.2, §7), and ADR-001 fixes the runtime as "Node.js 24 LTS with +Fastify 5, PostgreSQL 18, React 19 for server-rendered public components and a Vite +admin". The LTS strategy keeps React exact-pinned at 19.2.8, keeps React Server +Components out of v1, and keeps React out of persisted content formats +(Technology-Stack §6). + +The workspace does not implement the public renderer yet — `apps/server` is the bare +Fastify health shell — so this ADR records the decision ahead of the code that +implements it, formalising what the Architecture and Technology-Stack wikis already +mandate: public pages are server-rendered first, React is a server-rendering detail, +and the site is not hydrating (§19, §19.1). + +## Decision + +EPPP renders all public pages with **React SSR for public rendering**: server-side +rendering through the React DOM server renderer, React 19.2.8 exact-pinned (class C, +golden tuple). Public pages are server-rendered first (§19); the pipeline terminates +in the React DOM server renderer producing HTML, with no client-side hydration of +core public pages — React is a server-rendering detail and the site is not hydrating +(§19.1). Core public Home/article pages target 0 bytes of EPPP JavaScript, and only +truly interactive features register client islands with an explicit activation mode +(§19.2). React Server Components are out of scope for v1, and React stays out of +persisted content formats (Technology-Stack §6). +The decision text — **React SSR for public rendering** — matches the ADR index entry +(ADR-007, section 70). + +## Alternatives + +- **Client-side rendering (SPA)** — rejected: public pages must work with JavaScript + disabled (§19.1 zero-JavaScript baseline); a browser-rendered SPA delivers no HTML + to first paint and contradicts the server-rendered-first pipeline (§19). +- **Static site generation (build-time SSG)** — rejected: content is connection-backed + PostgreSQL 18 (ADR-005) resolved per request with visitor preferences and + extension-owned content; build-time HTML would go stale against the canonical DB and + add a rebuild/redeploy cycle per content change. +- **SSR with full client hydration** — rejected for v1: core public pages target + 0 bytes of EPPP JavaScript (§19.1); hydration would ship a client bundle to every + visitor when only a few interactive features need JS (§19.2 islands). +- **React Server Components (RSC)** — rejected for v1: the LTS strategy keeps RSC out + of v1 and out of persisted content formats (Technology-Stack §6); under the + zero-JavaScript baseline there is no client component tree to serve. +- **Template engine / string templating (e.g. Pug or Handlebars via @fastify/view)** — + rejected: the pipeline already terminates in the React DOM server renderer (§19), + themes resolve through the ThemeResolver into that renderer, and the golden tuple + pins React 19.2.8; a second rendering technology would duplicate work with no + benefit. + +## Consequences + +- Positive: server-rendered HTML works with JavaScript disabled, meeting the + zero-JavaScript baseline (§19.1); public pages get a fast first paint with no client + bootstrapping; metadata/SEO output is plain HTML; React is already in the golden + tuple (class C) so no new dependency or stack choice; one rendering path serves all + public pages, and themes/extensions compose through the documented pipeline + (PageComposition, BlockRegistry, ThemeResolver). +- Negative: SSR costs per-request CPU in the Node.js process; the React runtime must + load in the server process; rendering errors surface at request time rather than at + build time; public pages have no client interactivity without registered islands. +- Neutral: React stays a server-rendering detail — the site is not hydrating (§19.1); + browser JavaScript exists only on registered islands (§19.2); the decision constrains + the later client-side decisions (zero-JS public baseline and the client-island model, + ADR-009 and ADR-010). + +## Operational impact + +- One Node.js 24 process renders public HTML in-process via the React DOM server + renderer inside the single application image (ADR-001); no separate rendering + service or runtime build step. +- Public routes in the v0.1 surface (§42) return server-rendered HTML; core + Home/article pages ship 0 bytes of EPPP JavaScript (§19.1) — a measurable CI + invariant once the renderer lands. +- Rendering is stateless: horizontal scaling means more instances of the same image + behind the optional edge proxy; rendering load is part of the application process. +- React 19.2.8 is exact-pinned and class C (Technology-Stack §5.2, §7); upgrades flow + through the dependency lanes (ADR-026), and a React major is an ADR-recorded upgrade + program item (§7/§8). +- The renderer is not implemented yet — `apps/server` is the Fastify health shell — so + today rollback is reverting this documentation commit; once the renderer lands, + rollback is redeploying the previous image. + +## Revisit trigger + +- Revisit when a v0.1 public page cannot meet the zero-JavaScript baseline (§19.1) + with server rendering alone, or a required feature needs browser-side rendering at + scale. +- Revisit when React Server Components or full hydration is proposed for v1 — + Technology-Stack §6 explicitly keeps RSC out of v1. +- Revisit when the golden tuple's React pin moves to a new major (an ADR-recorded + upgrade program item, §7/§8) or React's support class changes. +- Revisit when visitor preferences or the v1.1 boundary contracts (ADR-027 to ADR-032) + demand a different rendering model — consistent with the Architecture §19 gates.