[]previous and next page

renderToStream

Renders an app as a stream of HTML, for the fastest first byte: the shell first (pending Suspense boundaries show their fallback), then one chunk per boundary as its data arrives, in the order the data settles, and finally, when resources resolved, a <script type="application/json" data-sh="RENDER_ID"> block with their values.

Renders an app as a stream of HTML, for the fastest first byte: the shell first (pending Suspense boundaries show their fallback), then one chunk per boundary as its data arrives, in the order the data settles, and finally, when resources resolved, a <script type="application/json" data-sh="RENDER_ID"> block with their values. The page works without JavaScript: each chunk is a <template> that a tiny inline script swaps in for the fallback. A boundary whose data can no longer arrive keeps its fallback for the client.

Import#

ts
import { renderToStream } from "@shadow-js/web/server";

Signature#

ts
function renderToStream(
  code: () => unknown,
  options: StreamOptions = {},
): ReadableStream<Uint8Array>

Parameters#

Name Description
code Builds the app.
options Hydration-key prefix, nonce, time budget, abort signal, collectors, shell callback and error reporters. Undefined uses the defaults of StreamOptions.

Returns#

A byte stream of the app markup, to be placed inside your document. Canceling it stops the render.

Throws#

  • TypeError, synchronously, for an invalid renderId or timeoutMs. The stream itself errors with an SsrError whose code is SSR_TIMEOUT when timeoutMs runs out, with the signal's reason when signal aborts, and with an error no boundary caught when onUncaughtError is not set.

Examples#

ts
import { createComponent, renderToStream } from "@shadow-js/web/server";

function App() {
  return <h1>Hello</h1>;
}
function handle(request: Request): Response {
  const stream = renderToStream(() => createComponent(App, {}), { signal: request.signal });
  return new Response(stream, { headers: { "content-type": "text/html" } });
}