09 — SSR¶
Server-side rendering in re-frame2 is the same framework as the client side — same registrations, same cascade, same app-db, same subs. The differences are: the request creates a frame (the per-request frame pattern), the cascade runs to completion before the response is built, the resulting hiccup is emitted as an HTML string, and a hydration payload is shipped with it so the client can pick up where the server left off without re-rendering.
This works because the framework was designed around immutable data and an explicit cascade boundary. The handler that runs server-side is the same handler the client would run; the only differences are which fx are gated to which platform (:platforms metadata) and which cofx are server-only (:rf.server/request).
This chapter covers the rendering primitives (render-to-string, the streaming triple, the structural-hash), the head model (reg-head, active-head, render-head), the per-request response accumulator and its server-only fx, the error-projection seam (reg-error-projector, project-error), and the :platforms metadata that gates fx execution by active platform.
The normative source is 011-SSR.md. The SSR surfaces live in re-frame.ssr (artefact day8/re-frame2-ssr); the Ring host-adapter lives in re-frame.ssr.ring. Neither is re-exported from re-frame.core — apps targeting SSR add the artefacts to their deps and require the namespace directly.
Rendering primitives¶
render-to-string¶
- Kind: function
- Signature:
- Description: The canonical server-side render. Walks the hiccup tree once, emits a string. JVM-runnable. Test-friendly because it's pure.
- Example:
- In the wild: ssr
render-tree-hash¶
- Kind: function
- Signature:
- Description: A deterministic structural fingerprint of a render tree. Same canonical-EDN representation produces the same hash on JVM and CLJS. Used by the hydration compatibility check — if the server's hash doesn't match the client's hash, hydration is unsafe.
project-error¶
- Kind: function
- Signature:
- Description: Apply the active error-projector (selected by the frame's
:ssr {:public-error-id ...}metadata) for the named frame. This is the seam between "internal error trace event with full diagnostic detail" and "client-safe public-error projection."
Streaming render¶
Larger pages benefit from streaming — emit the shell-html and continue rendering boundary subtrees as their data becomes available. Re-frame2's streaming model uses :rf/suspense-boundary markers in the render tree; each boundary becomes a continuation.
streaming-render-shell¶
- Kind: function
- Signature:
- Description: Walk the tree once; at each
:rf/suspense-boundaryemit a<template …suspense-fallback>placeholder and record a continuation. Returns the shell-html (ready to flush) and the continuations to drain. - In the wild: ssr_streaming
streaming-render-continuation¶
- Kind: function
- Signature:
- Description: Drain one continuation against
frame-id's app-db. Snapshots before-db / after-db and computes the per-subtree delta. Catches throws and surfaces the original fallback HTML inline (per 011 §Failure semantics — inline fallback).
streaming-build-final-payload¶
- Kind: function
- Signature:
- Description: Called after all continuations drain to populate the
__rf_payloadfinal chunk.
The streaming surface is host-adapter territory — the SSR-aware host (re-frame.ssr.ring or equivalent) wires it. Most app code interacts via the host adapter and never touches streaming-render-* directly.
The head model¶
The <head> of an SSR document is structurally separate from the body. Re-frame2 models it as a head-model — a data structure carrying :title, :meta, :link, :json-ld, :html-attrs, :body-attrs — registered per-route and rendered separately from the body.
reg-head¶
- Kind: macro
- Signature:
- Description: Register a head-fn keyed by id. Signature:
(fn [db route] head-model). Routes opt-in via:headroute metadata. - Example:
render-head¶
- Kind: function
- Signature:
- Description: Evaluate the registered head-fn for
head-id. Returns a head-model.
active-head¶
- Kind: function
- Signature:
- Description: Resolve the head-model for the currently active route in the named frame. Sub-shape:
[:rf/head]subscribes to this.
head-model->html¶
- Kind: function
- Signature:
- Description: Render a head-model to its HTML representation.
:wrap?controls whether<head>tags are emitted (default: false, so the result can be composed into a larger shell).
head-snapshot¶
- Kind: function
- Signature:
- Description: Read the per-frame snapshot of last-produced head-models. Returns
{}for a frame that has never seen arender-headcall. Useful for tests, introspection, and tools. Re-exported asrf/head-snapshot.
Standard SSR events¶
| Event | What it does | Spec |
|---|---|---|
:rf/server-init |
Per-request server-side initialisation. Reads request cofx; dispatches setup events. :platforms #{:server}. |
011 |
:rf/hydrate |
Seed the client-side app-db from the server-supplied payload. Runs once on client bootstrap. |
011 |
Standard SSR fx (server-only)¶
All server-only — :platforms #{:server}. These build the response accumulator that the host adapter turns into the HTTP response.
| Fx | Args | Spec |
|---|---|---|
[:rf.server/set-status int] |
per :rf.fx.server/set-status-args |
011 |
[:rf.server/set-header {:name :value}] |
per :rf.fx.server/set-header-args |
011 |
[:rf.server/append-header {:name :value}] |
per :rf.fx.server/append-header-args |
011 |
[:rf.server/set-cookie :rf.server/cookie] |
structured cookie map | 011 |
[:rf.server/delete-cookie {:name ?:path ?:domain}] |
— | 011 |
[:rf.server/redirect {:location ?:status}] |
default :status 302; truncates HTML. Caller-trusted :location. |
011 |
[:rf.server/safe-redirect {:location ?:relative-only? ?:allow}] |
The caller-untrusted variant — parses :location, rejects javascript: / data: / vbscript: schemes, and enforces :relative-only? / :allow allowlist before setting :redirect. Open-redirect mitigation for attacker-controlled ?next= strings. |
011 |
Standard SSR subs¶
| Sub | Returns | Spec |
|---|---|---|
:rf/response |
The current request's response accumulator (status / headers / cookies / redirect) | 011 |
:rf/head |
The head model for the active route (resolved via (active-head)) |
011 |
:rf/public-error |
The sanitised public-error projection when an error page is being rendered; nil otherwise |
011 |
Standard SSR cofx¶
| Cofx | Returns | Spec |
|---|---|---|
:rf.server/request |
The active HTTP request map. | 011 |
:platforms metadata on reg-fx¶
reg-fx accepts a :platforms metadata key — a set containing :server and / or :client — that gates fx execution by active platform. Default #{:server :client} (universal) when the key is absent.
Skipped fx emit a :rf.fx/skipped-on-platform trace event so debug tools see the gate firing. The cofx side has a mirror trace event, :rf.cofx/skipped-on-platform.
Detail in 011 §:platforms metadata on reg-fx.
Per-frame error-projection policy¶
A frame opts into SSR error projection via the :ssr {:public-error-id ... :dev-error-detail? ...} map on its reg-frame / make-frame metadata. This is per-frame metadata, not a configure key — different frames in the same process can carry different projector / dev-detail settings, so the natural lifetime is per-frame.
reg-error-projector¶
- Kind: macro
- Signature:
- Description: Register a projector keyed by id. Signature:
(fn [trace-event] :rf/public-error). Named per-frame via the frame's:ssr {:public-error-id ...}metadata. - Example:
The companion project-error accessor is rowed above in §Rendering primitives — same signature; same job. Apply the active projector for the named frame.
Full rationale: Conventions §Configuration surfaces bucket 3 and 011 §Server error projection.
Hydration¶
The server-rendered HTML carries a __rf_payload chunk that the client deserialises into app-db on bootstrap. The structural-hash from render-tree-hash is captured at render time and checked at hydration; mismatch surfaces :rf.ssr/version-mismatch or :rf.ssr/schema-digest-mismatch rather than silently mounting a broken DOM.
The hydration payload shape lives at Spec-Schemas §:rf/hydration-payload.
See also¶
- 01 — Core —
reg-head/reg-error-projectorrowed in registration. - 06 — Routing — routes opt into head models via
:headmetadata. - 11 — Instrumentation — the SSR-specific trace events live in the error catalogue.
- Spec 011 — SSR — the normative source.