Skip to content
Vincle

Loading…

    From a view to progressive HTML

    Start with a component and an HTTP response. When part of the page becomes slow, defer that component and send the surrounding HTML first.

    Set up JSX#

    Follow Installation and TypeScript setup first. The examples below live together in the same directory.

    Compose a view#

    Components are functions with TypeScript props. They can load their own data.

    results.tsx
    // Replace this sample data source with your database or search service.
    async function findResults() {
    return [{ title: "Getting started", href: "/guide/getting-started/installation" }];
    }
    export async function Results() {
    const items = await findResults();
    return (
    <ul>
    {items.map((item) => (
    <li>
    <a href={item.href}>{item.title}</a>
    </li>
    ))}
    </ul>
    );
    }

    Return HTML from a route#

    Call this handler from your server's routing layer. It uses the standard Response API; servers with their own response helpers can send the same HTML string.

    import { renderToString } from "@vincle/core";
    import { Results } from "./results";
    export async function GET() {
    const html = await renderToString(
    <main>
    <h1>Search results</h1>
    <Results />
    </main>,
    );
    return new Response(html, {
    headers: { "Content-Type": "text/html; charset=utf-8" },
    });
    }

    This response contains an HTML fragment. Wrap the view in your document layout when serving a complete page.

    The response waits for the complete tree. Async components execute in document order; making a component async does not run its siblings in parallel.

    Let the results arrive later#

    Install the optional streaming package:

    Terminal window
    npm install @vincle/flow

    Keep Results unchanged and wrap it in Defer. Use renderToStream instead of renderToString to send the surrounding markup and fallback before the results.

    import { Defer, renderToStream } from "@vincle/flow";
    import { NativeAdapter } from "@vincle/flow/adapters";
    import { Results } from "./results";
    function Page() {
    return (
    <main>
    <h1>Search results</h1>
    <Defer fallback={<p>Loading results…</p>}>
    <Results />
    </Defer>
    </main>
    );
    }
    const stream = renderToStream(() => <Page />, NativeAdapter);

    The stream contains strings. In a server that accepts Web Streams, return it as a byte stream from your route:

    return new Response(stream.pipeThrough(new TextEncoderStream()), {
    headers: { "Content-Type": "text/html; charset=utf-8" },
    });

    Your server and proxy must pass chunks through without buffering the whole response.

    The Native adapter supplies the browser patching script. If your application already uses HTMX or Turbo, choose the corresponding adapter and load its client library.

    To cancel a deferred component's work, use a function child: it receives an AbortSignal to forward to fetch. See Streaming for cancellation and timeouts.

    Continue from here#