Repository navigation
inertia: full server-side rendering via a Node sidecar (deferred; design attached) #580
Copy link
Copy link
Open
Labels
enhancementNew feature or requestNew feature or request
Description
Backlog. #535 step 1 (a server-rendered <head>: Head, public_base_url, client re-emit) shipped in #574. Full server-side rendering was designed and then deferred. This issue keeps the design so it can be picked up later.
Design summary (reviewed 2026-10-22, not yet approved for build)
Which pages render on the server
- A page opts in with
inertia.render(PAGE, props, head=..., ssr=True). Landing would be first. - An opted-in page renders on the server for anonymous and signed-in visitors. Its props are already embedded in the HTML.
- Admin and signed-in-only pages stay client-rendered.
Python
InertiaConfig.ssr_rendererhook.Noneor an exception falls back to client rendering, and anX-Inertiarequest never triggers SSR.- New
simple_module_hosting/_ssr.py:- Sends a POST to
{ssr_url}/renderthrough a lifespan-scopedhttpx.AsyncClient. - Checks the response shape. It must be
{head: list[str], body: str}, withbodystarting<script data-page=. - Circuit breaker: after 3 failures in a row, skip SSR for 30 s.
- Health check
ssrreports DEGRADED when the sidecar is down.
- Sends a POST to
- New HostSettings:
ssr_url: empty means off. Restart-required, which also avoids a runtime SSRF knob.ssr_enabled: per-request kill switch.ssr_timeout_ms: default 1500.
- Template fix: when the SSR head is used, it replaces
head_tags; it is not appended. Suppress the brand<title>when the SSR head carries one. - Deprecate the dead
InertiaConfig.ssr_url/ssr_enabledwith aremove_in, and drop themanifest.py:50coupling.
JS
-
host/client_app/ssr.tsxvia@inertiajs/react/server(createServer, port 13714). Put the render function inssr-render.tsxso it can be tested. -
Move the shared
createInertiaAppoptions intoinertia-options.ts. -
app.tsxcallshydrateRootwhen the root element hasdata-server-rendered. -
Vite SSR build (
vite.ssr.ts,ssr.noExternal: true) intohost/ssr-dist, never understatic/.npm run buildbuilds the client and SSR bundles together. -
SSR-safety fixes:
Where Fix i18next (one process-wide instance) Call configureI18nsynchronously in SSRsetup(), or usecreateInstance+I18nextProvider.app-title.ts(module-levelappName)Set it per render. Year computed at module scope ( BrandingFooter.tsx:5,AuthSplitAside.tsx:8)Compute it at render time. use-relative-timeAvoid a hydration mismatch. BrandingHeadbrand colours, applied only in an effectFollow-up: render them server-side.
Operations
- Docker
ssrtarget: Node 24, non-root,HEALTHCHECK /health. - A compose
ssrservice with no host port, because of the sidecar's unauthenticated/shutdown.appgetsSM_SSR_URL. smpy docker add-ssrfor scaffolded apps.make devunchanged; a new opt-inmake dev-ssr.
Tests
- Python: a fake renderer covering the fallbacks, the breaker, and no duplicate head.
- Node-environment Vitest: render Landing on the server, and check no locale bleed between
deanden. - e2e: the raw HTML contains the content, and the page works with JS disabled.
Trade-offs
- Each opted-in page adds one round trip to Node, bounded by the timeout and the breaker. There is no render cache in version 1.
- The server and client bundles must come from the same build.
- Every render POST carries the full i18n catalogue.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or request
Activity