Coming from Next.js¶
Next.js and re-frame2 SSR do the same jobs: render HTML on the server, hydrate it
on the client, and load data before the page paints.
The main structural difference is that re-frame2 has no separate server layer. The
event handlers,
subscriptions and
views you write for the browser run unchanged on the
JVM, against a per-request frame. There is no
Server/Client component split; code that must run on one side only is declared on
the effect with :platforms.
The mapping¶
| Next.js | re-frame2 |
|---|---|
| Server Component | Any event handler, subscription or view. They are pure, so they run on either side; there is no separate component kind. |
Client Component ("use client") |
The same view, after hydration. Browser-only effects are declared with :platforms #{:client}. |
"use server" / "use client" directives |
:platforms on an effect or coeffect, declared once where the effect is registered. |
getServerSideProps (Pages Router) |
Your ordinary events, dispatched by the per-request frame's :initial-events and drained before the render. The drain settles synchronous work only; data fetched over the network belongs in a route's blocking resource (next rows). |
A page's data fetching (fetch in a Server Component) |
A route loader: the :resources the route declares, loaded on route entry (on the server during the request, on the client on navigation). |
awaiting data in a Server Component before it renders |
A route resource declared :blocking? true. The Ring handler waits for it, within a 5-second budget, before rendering. A fetch your :initial-events start themselves is not waited for. See blocking resource. |
Promise.all of N fetches |
The SSR loader pattern: several :blocking? true entries in the route's :resources. They load in parallel, so the wait is the slowest fetch, and the same declaration drives the fetch on client navigation. |
Server Action (form action={fn}) |
The form-action pattern: a real method="POST" form routes to the same event the client's :on-submit dispatches. |
hydrateRoot |
Two calls. ssr/hydrate! installs the server's state from the payload before the first render; the adapter's render! with {:hydrate? true} then adopts the DOM. |
Several hydrateRoot calls on one page (islands) |
ssr/hydrate-page!: each root hydrates and mounts inside its own failure boundary, usually into one shared frame. |
Reading cookies() / headers() |
A declared coeffect: :rf.cofx/requires [:rf.server/request], with the value in the handler's coeffects. The value is the Ring request, so :cookies is there only when Ring's wrap-cookies runs in front. |
cookies().set(...) |
The :rf.server/set-cookie effect, which takes a map. See Controlling the response. |
redirect() / notFound() |
The :rf.server/redirect effect; [:rf.server/set-status 404], or an unmatched URL, which the default error projector answers with 404. |
error.js / global-error.js |
The error projector maps the failure to a sanitised public error, and a 5xx renders ssr-handler's :error-view from that alone. |
The Metadata API / generateMetadata |
reg-head: a head model derived from app-db, a pure function of (db, route). |
The root layout.js / _document.js document |
The page shell: default-html-shell, adjusted with :head, :body-end, :script-src and :app-element-id, or replaced with :html-shell. |
<Suspense fallback> + loading.js (streaming) |
A route resource with :blocking? false: the server renders its loading state and the client fetches it after hydration (A slow region). re-frame2 does not stream regions. |
| Hydration mismatch (console warning, content flash) | A hydration mismatch trace for each recovery React makes while a root hydrates, plus an always-on error record. CI fails a build from the root's :on-recoverable-error callback. |
unstable_cache / fetch cache |
A resource: loaded on the server, shipped in the payload, and rendered on the client without a second fetch. |
next/server runtime, route handlers, middleware |
The Ring adapter, day8/re-frame2-ssr-ring: ssr-handler returns a Ring handler, and ssr-middleware mounts it inside an existing Ring app. See Ring handler. |
| Rendering in Node | The JVM renders by default. A native view layer such as Fresco renders its body on a Node sidecar through the Node renderer, while the JVM keeps the request, the payload and the response. |
next build / NODE_ENV=production |
A production build: an :advanced client bundle, and a server JVM in production posture, its default. Development opts in with -Dre-frame.debug=true. |
Differences to know¶
One declaration loads data on both sides. A route's :resources drive the
fetch on the server during the request and on the client during navigation, so
there is no server-only data function to keep in step with a client one. The
per-request frame runs its :initial-events, drains, waits for the route's
blocking resources, and then renders.
The client receives the server's state as well as its HTML. hydrate! installs
the server's app-db and the serialisable
runtime-db slice before the first render, so the
client does not fetch again to catch up. A mismatch React recovers from while the
root hydrates is reported as a trace; see
When the renders disagree.
What reaches the client is an allowlist. Next.js serialises whatever props your
loader returns. re-frame2's :payload
names the top-level app-db keys that may ship; every other key stays on the server,
including keys added later. There is no denylist form, and constructing a handler
without :payload throws at boot.
The head is not updated on client navigation. generateMetadata re-runs on an
App Router navigation and Next.js updates the live document head. re-frame2 ships
no DOM-head reconciler, so refreshing <title> and <meta> after a client-side
route change is the app's job, reading the same head model. Hydration reports
mismatches inside the body's root only. Head metadata covers both.
Response control is data. Status, headers, cookies and redirects are
server-only :rf.server/* effects returned from
handlers. Cookies are maps, and a CR, LF or NUL in a header value, redirect
location or cookie attribute throws. See Controlling the response.