@vertz/ui-server/node provides createNodeHandler — a native Node HTTP adapter that writes SSR output directly to ServerResponse, avoiding the overhead of web Request/Response conversions on every request.
When to use this
UsecreateNodeHandler when deploying to any Node.js-based platform:
- Vanilla Node HTTP server
- Express / Fastify / Koa
- Docker containers
- AWS Lambda (with a Node HTTP adapter)
- Any platform that gives you
IncomingMessage+ServerResponse
createSSRHandler instead — those runtimes handle Request/Response natively with no conversion cost.
Quick start
- Renders SSR HTML directly into
ServerResponse(no intermediateResponseobject) - Streams progressive HTML when enabled
- Handles nav pre-fetch SSE requests
- Manages backpressure and client disconnects
Why not createSSRHandler?
createSSRHandler returns a web-standard (Request) => Promise<Response> handler. On Bun and Cloudflare Workers, Request and Response are native — zero conversion cost.
On Node.js, bridging between Node’s IncomingMessage/ServerResponse and web Request/Response adds two conversions per request:
- Inbound:
IncomingMessage→Request(URL construction, header copying, body stream wrapping) - Outbound:
Response.body(ReadableStream) → pipe toServerResponse(async iteration, chunk copying)
createNodeHandler eliminates both. It reads from IncomingMessage and writes directly to ServerResponse — the same rendering pipeline, without the bridge overhead.
Configuration
createNodeHandler accepts the same options as createSSRHandler:
The
sessionResolver receives a web Request object — the Node adapter constructs one from
IncomingMessage headers for this call only. This is the only web API conversion, and it’s
limited to session resolution.Integration with frameworks
Express
Fastify
Progressive HTML streaming
WhenprogressiveHTML: true, the handler streams HTML in three phases:
- Head —
<head>content (CSS, preloads, fonts) sent immediately for early browser parsing - Body — rendered HTML chunks streamed as they’re produced, with backpressure handling
- Tail — SSR data script and closing tags
res.write() returns false, it waits for the drain event before writing the next chunk. If the client disconnects mid-stream, the render stream is cancelled immediately.
Production considerations
Compression
createNodeHandler does not compress responses. Use your platform’s compression:
Graceful shutdown
Keep-alive
Node’s HTTP server has keep-alive enabled by default. For long-lived SSE connections (nav pre-fetch), the handler detects client disconnects viareq.on('close') and cleans up the stream reader.
How it differs from the web-standard handler
createSSRHandler | createNodeHandler | |
|---|---|---|
| Signature | (Request) => Promise<Response> | (IncomingMessage, ServerResponse) => void |
| Best for | Bun, Cloudflare Workers, Deno | Node.js, Express, Fastify |
| Conversion cost | None (native) | None (writes directly) |
| Backpressure | Web stream API | Node drain events |
| Progressive HTML | ReadableStream response body | Direct res.write() chunks |
ssr-handler-shared.ts) and produce identical output.
Cloudflare Workers
Deploy to Cloudflare with SSR, API routing, and ISR caching
SSR
How Vertz SSR works under the hood