docs(adr): record React SSR for public rendering decision as ADR-007
CI / Stage 6 — PostgreSQL integration tests (E00-S05-T01) (pull_request) Successful in 1m14s
CI / Stage 4 — Unit tests (E00-S05-T01) (pull_request) Successful in 1m37s
CI / Stage 7 — Build the admin and server applications (E00-S05-T01) (pull_request) Successful in 1m3s
CI / Stage 5 — Architecture tests (E00-S05-T01) (pull_request) Successful in 3m14s
CI / Stage 2 — Typecheck (E00-S05-T01) (pull_request) Successful in 1m20s
CI / Stage 1 — Frozen lockfile install (E00-S05-T01) (pull_request) Successful in 51s
CI / Stage 3 — Formatting/lint policy (E00-S05-T01) (pull_request) Successful in 45s
CI / Stage 6 — PostgreSQL integration tests (E00-S05-T01) (pull_request) Successful in 1m14s
CI / Stage 4 — Unit tests (E00-S05-T01) (pull_request) Successful in 1m37s
CI / Stage 7 — Build the admin and server applications (E00-S05-T01) (pull_request) Successful in 1m3s
CI / Stage 5 — Architecture tests (E00-S05-T01) (pull_request) Successful in 3m14s
CI / Stage 2 — Typecheck (E00-S05-T01) (pull_request) Successful in 1m20s
CI / Stage 1 — Frozen lockfile install (E00-S05-T01) (pull_request) Successful in 51s
CI / Stage 3 — Formatting/lint policy (E00-S05-T01) (pull_request) Successful in 45s
This commit is contained in:
@@ -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.
|
||||||
Reference in New Issue
Block a user