Skip to content
Vincle

Loading…

    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.