Streaming SSR
Wrap a page in Streamed.of(() -> ...) and the shell flushes to the browser immediately. Every Suspense block renders its loading placeholder instantly and streams its real HTML into the page the moment its data resolves — blocks load in parallel and arrive in completion order. No JavaScript is written.
Usage
app.get("/dashboard", req -> Streamed.of(() ->
new Layout("Dashboard", div(
header(), // paints immediately
Suspense.of(() -> reports.slowQuery()) // streams in when ready
.loading(() -> spinner("Crunching numbers..."))
.render(data -> reportTable(data))
)).render()));How It Works
- The shell (with placeholders) is written and flushed first — TTFB ~35ms
- Each Suspense block runs on a virtual thread, in parallel
- Completed blocks stream as <template> chunks plus a tiny swap script
- Total time = the slowest block, not the sum
The page must be built inside the supplier — the element DSL evaluates eagerly, so a pre-built tree would already have executed its Suspense blocks before streaming starts.
See it live: /demo/streaming renders a 400ms and a 1200ms block. Outside a Streamed page, Suspense behaves exactly as before (blocking or nonBlocking).
When To Use It
Streaming shines when a page mixes fast content (navigation, headers, cached data) with slow content (reports, external APIs, heavy queries). The user sees and can interact with the page immediately instead of staring at a blank tab for the slowest query's duration.