renderToStream
Returns a ReadableStream<string>: the shell first, as a single chunk, then one
chunk per fragment as each resolves.
Signature#
import { renderToStream } from "@vincle/flow";
function renderToStream( node: () => JSX.Element, adapter: StreamingAdapter, opts?: FlowOptions & { mode?: "full" | "fragment" },): ReadableStream<string>;Options#
| Option | Type | Default | Description |
|---|---|---|---|
signal |
AbortSignal |
N/A | Signal to abort the stream |
onError |
FlowOptions["onError"] |
N/A | Handler for fragment / stream render errors |
defaultTimeout |
number |
N/A | Per-fragment render timeout in ms |
mode |
"full" | "fragment" |
"full" |
"fragment" emits neither shell nor close |
FlowOptions is { signal?, onError?, defaultTimeout? }. The adapter is a
required second argument, not an option. Options are validated at the call,
before anything renders: see Error handling.
Usage#
import { Slot, Defer, renderToStream } from "@vincle/flow";import { NativeAdapter } from "@vincle/flow/adapters";
declare function fetchComments(): Promise<{ text: string }[]>;
async function Comments() { const items = await fetchComments(); return ( <ul> {items.map((c) => ( <li>{c.text}</li> ))} </ul> );}
// <Slot> declares the placeholder with fallback content in the shell.// <Defer> pushes the real content, which replaces it when resolved.function Page() { return ( <html> <body> <h1>My page</h1> <Slot name="comments"> <p>Loading comments…</p> </Slot> <Defer target="comments">{() => <Comments />}</Defer> </body> </html> );}
const stream = renderToStream(() => <Page />, NativeAdapter);
// e.g. Node: for await (const chunk of stream) res.write(chunk);It returns the stream only: headers are yours to set, or use
serve(), which sets content-type: text/html; charset=utf-8.
The shell is one chunk#
The shell is never streamed progressively, transformShell included: it is
rendered in full, <head> and all, and only then emitted. A fully synchronous
page is a single chunk. Only fragments arrive later. Async siblings in the
shell resolve in document order, not concurrently; for regions that render in
parallel and patch into place, use <Defer> and <Slot>.
A <Defer> whose content is an AsyncIterable streams one patch per item,
with its merge type, and keeps the stream open until the iterable is
exhausted or the client disconnects.