Skip to main content
@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

Use createNodeHandler 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
If you’re deploying to Cloudflare Workers or another runtime with native web APIs, use the web-standard createSSRHandler instead — those runtimes handle Request/Response natively with no conversion cost.

Quick start

That’s it. The handler:
  • Renders SSR HTML directly into ServerResponse (no intermediate Response object)
  • 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 runtimes with native web APIs (Cloudflare Workers, Deno, etc.), 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:
  1. Inbound: IncomingMessageRequest (URL construction, header copying, body stream wrapping)
  2. Outbound: Response.body (ReadableStream) → pipe to ServerResponse (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

When progressiveHTML: true, the handler streams HTML in three phases:
  1. Head<head> content (CSS, preloads, fonts) sent immediately for early browser parsing
  2. Body — rendered HTML chunks streamed as they’re produced, with backpressure handling
  3. Tail — SSR data script and closing tags
The Node adapter handles backpressure natively: when 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 via req.on('close') and cleans up the stream reader.

How it differs from the web-standard handler

createSSRHandlercreateNodeHandler
Signature(Request) => Promise<Response>(IncomingMessage, ServerResponse) => void
Best forCloudflare Workers, DenoNode.js, Express, Fastify
Conversion costNone (native)None (writes directly)
BackpressureWeb stream APINode drain events
Progressive HTMLReadableStream response bodyDirect res.write() chunks
Both handlers share the same rendering pipeline (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