Skip to content

Server-Side Rendering

Ship HTML before the JavaScript loads, then let the client take over — without writing your app twice. That's the promise SSR usually breaks: you end up maintaining a server render path and a client render path that drift, plus a tangle of typeof window checks. re-frame2's rule is "one app, runs twice." The same events, subscriptions, and views run on the JVM (server) and in the browser (client); only genuinely one-sided code is fenced off with :platforms.

On the server, render-to-string runs your real views in a per-request frame and emits HTML plus a serialized state payload. On the client, hydration adopts that HTML and installs the state instead of re-rendering from scratch — and a hydration mismatch is detected and traced, never silently patched.

;; server (JVM): your real views, rendered inside a per-request frame — no DOM anywhere
(rf/with-frame f
  (ssr/render-to-string [(rf/view :app/root)] {}))

;; client: adopt the server's HTML and its state instead of re-rendering
(ssr/hydrate! {:frame :app :render-tree-fn (fn [] ((rf/view :app/root)))})

In this section

  • Tutorial: render on the server — build it by hand, one step at a time: a pure render at the REPL, a frame per request, the state payload, hydration, a deliberately-tripped mismatch, and the production Ring adapter. Start here.
  • Concepts — the model in dependency order: why the same code runs on a JVM, one request start to finish, the payload allowlist, hydrate-then-verify, response control, head metadata, error projection, and streaming.
  • Examples — runnable end-to-end SSR apps in the repo.
  • Glossary — the section's vocabulary, one definition each.
  • Coming from Next.js — the translation table and the deliberate divergences.

The API-level surface lives in re-frame.ssr and re-frame.ssr.ring: the guide teaches, the reference is where you look things up.