re-frame.ssr.ring.node¶
Render the page body on a Node sidecar instead of the JVM, for a view layer whose components are JavaScript, such as a Fresco root. renderer returns a value for ssr-handler's :renderer: for each request it sends the settled request frame's state to the Node render sidecar and takes back the body markup. The head, payload, shell, status, headers and error projection stay on the JVM. Render on Node walks through the bundle, the sidecar and deployment.
Ships in the day8/re-frame2-ssr-ring artefact, beside re-frame.ssr.ring. The render sidecar is not published; Start the sidecar, and mind the skew covers where it comes from and its flags.
(:require [re-frame.core :as rf]
[re-frame.ssr :as ssr]
[re-frame.ssr.ring :as ssr.ring]
[re-frame.ssr.ring.node :as node])
Assuming the app's :app/init event is registered, the sidecar is running, and MY_APP_BUILD_ID matches its bundle:
(rf/init! ssr/adapter)
(ssr.ring/ssr-handler
{:initial-events [[:app/init]]
:payload [:todos]
:renderer (node/renderer
{:entry "my-app/root"
:build-id (System/getenv "MY_APP_BUILD_ID")
:render-state {:app-db [:todos :session]
:runtime-db [:rf.runtime/routing]}})})
The renderer¶
renderer¶
- Kind: function
- Signature:
- Description: Returns a value for
ssr-handler's:renderer. It validatesoptsand builds one HTTP client at construction; per request it projects the frame's state under:render-state, POSTs it to<endpoint>/renderand returns the sidecar's body verbatim.:render-hashis alwaysnil, so the page carries nodata-rf-render-hashand no payload:rf/render-hash.- With a map policy, the projection applies the handler's
:payload-include-sensitive, so the markup and the payload agree on a permitted value. - The render module reads the state back with
re-frame.ssr.render-state/deserialize. - A request waits at most
:timeout-ms+:admission-ms+ 600 ms, body included.
- Options:
:entry(required) — the bundle entry to render; a non-empty string.:build-id(required) — a non-empty string equal to the server bundle's build id.:render-state(required) — what the render may see:{:app-db [<keys>] :runtime-db [<keys>]}, fail-closed allowlists of top-level keys with either slot optional, or(fn [frame-id] → {:rf/app-db {…} :rf/runtime-db {…}}). It is a policy separate from the handler's:payload. Every value must read back equal through EDN: no fn, record,#inst,#uuid,Ratio,BigDecimal,BigInt,BigInteger,Float,##NaN, or integer outside −(2^53 − 1) through 2^53 − 1 (Two policies, and why they differ).- The map must name
:app-dbor:runtime-db(or both). Each present allowlist is a non-empty sequential of keywords; vectors, lists and lazy sequences are accepted. Omit a partition to send it as{}. An allowlisted key absent from the live frame is omitted. - Other policy keys, sets, empty allowlists, and
:rf.runtime/elisionin the runtime allowlist are invalid. There is no whole-partition keyword policy. - A projector fn must return a map with only
:rf/app-dband:rf/runtime-db; either may be omitted, but a present partition must be a map with keyword top-level keys. Its result is validated for the wire, but the fn owns selection and redaction: the automatic classification projection applies only to the map policy.
- The map must name
:endpoint— the sidecar's absolutehttporhttpsURL; default"http://127.0.0.1:8148". A non-loopback URL is accepted; securing it is the operator's job.:args— root arguments sent as EDN, under the same value rule.:timeout-ms— the render deadline sent to the sidecar; a positive integer, default1000.:admission-ms— how long the sidecar may queue the request; a non-negative integer, default250. Match the sidecar's--admission-ms.
- Errors:
- At construction:
:rf.error/ssr-node-renderer-opt-invalid(ex-data:opt,:got) for a malformed option;:rf.error/ssr-missing-payload-policyor:rf.error/ssr-malformed-payload-allowlist, with:opt :render-state, for a missing or malformed:render-state. - Per request, thrown at the render call. The handler projects each as
:rf.error/ssr-render-failed, with the id below in its:exception, so the request answers the 5xx error page and:on-erroris not called::rf.error/ssr-node-unreachable— no HTTP answer: connection refused, connect timeout, an I/O fault.:rf.error/ssr-node-deadline— the render missed its deadline;:observed-byis:sidecar(its504) or:jvm.:rf.error/ssr-node-refused— any other non-200; carries:statusand the sidecar's:refusalcode.:rf.error/ssr-node-build-skew— a200whosex-rf-ssr-buildheader is missing or is not:build-id.:rf.error/ssr-render-state-invalid— a projected value the EDN wire cannot carry, or a:render-statefn that returned a malformed envelope.
- At construction:
Defaults¶
renderer takes :endpoint, :timeout-ms and :admission-ms from these vars when you leave them out, and adds wire-margin-ms to derive its HTTP timeout.
| Var | Value | Meaning |
|---|---|---|
default-endpoint |
"http://127.0.0.1:8148" |
The sidecar launcher's default bind, used when :endpoint is absent. |
default-timeout-ms |
1000 |
Used when :timeout-ms is absent. It matches the launcher's --timeout-ms default. |
default-admission-ms |
250 |
Used when :admission-ms is absent. It matches the launcher's --admission-ms default; raise both together. |
wire-margin-ms |
500 |
The fixed allowance the HTTP timeout adds for the wire itself. |
(http-timeout-ms opts) → msreturns the HTTP timeout each request carries::timeout-ms+:admission-ms+wire-margin-ms. Pass itoptswith both keys present, asrenderermerges them. The sidecar is given the smaller budget, so it refuses a late render before the JVM stops waiting.(validate-opts! opts) → optsruns the construction checksrendererruns and returnsoptsunchanged, throwing the construction errors listed underrenderer. It does not merge the defaults itself, so pass itoptsmerged over them, asrendererdoes.