Skip to content
Vincle

Loading…

    Components

    Components for deferring content in the HTML shell. Imported from @vincle/flow.

    Defer#

    Renders a placeholder holding fallback, and patches it later with its children:

    import { Defer } from "@vincle/flow";
    async function HeavyDashboard() {
    const data = await fetch("https://api.example.com/dashboard");
    const json = await data.json();
    return <pre>{JSON.stringify(json, null, 2)}</pre>;
    }
    // fallback is shown in the shell immediately.
    <Defer target="dashboard" fallback={<p>Loading dashboard…</p>}>
    <HeavyDashboard />
    </Defer>;

    children is plain JSX, a promise, or an AsyncIterable (one patch per item), or a factory (signal: AbortSignal) => … returning any of those. The signal aborts when the request is cancelled or the timeout expires. target is optional: without it, Vincle generates the placeholder id.

    Props:

    Prop Type Default Description
    target string Generated id Optional placeholder id; match a Slot name to fill it
    fallback JSX.Element N/A Content rendered in the placeholder
    children JSX.Element | string | ((signal: AbortSignal) => JSX.Element) | AsyncIterable<JSX.Element> | ((signal: AbortSignal) => AsyncIterable<JSX.Element>) N/A Content: plain JSX, a factory, or an async iterable
    merge "replace" | "append" | "prepend" | "before" | "after" | "morph" "replace" How content applies to the target
    timeout number N/A Per-fragment render timeout in ms
    onError (error: unknown, info: FlowErrorInfo) => JSX.Element | void N/A Per-fragment error handler

    Slot#

    A placeholder declared apart from its Defer: typically a layout reserving a spot that a page fills later, by matching target to name. Its children are shown until the matching Defer patches them.

    import { Slot, Defer } from "@vincle/flow";
    async function LiveComments() {
    const res = await fetch("https://api.example.com/comments");
    const comments: Array<{ id: string; text: string }> = await res.json();
    return (
    <ul>
    {comments.map((c) => (
    <li key={c.id}>{c.text}</li>
    ))}
    </ul>
    );
    }
    function Page() {
    return (
    <html>
    <body>
    <Slot name="comments">
    <p>Loading comments…</p>
    </Slot>
    <Defer target="comments" timeout={5000}>
    <LiveComments />
    </Defer>
    </body>
    </html>
    );
    }

    The Slot is the placeholder: its Defer emits none of its own, wherever the two sit in the page.

    Props:

    Prop Type Default Description
    name string N/A Fragment id, required
    children VNode N/A Fallback content rendered immediately in the shell

    Style / Script: asset emission#

    <Style> and <Script> emit their tag where they stand, once per name: the first occurrence wins, later ones render nothing.

    import { Style, Script } from "@vincle/flow/components";
    const Page = () => (
    <>
    <Style name="page.css" media="screen">
    {`.hero { color: red; }`}
    </Style>
    <Script name="page.js" defer src="/page.js" />
    <main>…</main>
    </>
    );

    Style props:

    Prop Type Default Description
    name string N/A Asset id, required, dedup key
    media string N/A media attribute
    children string | (() => Awaitable<string>) N/A CSS text, evaluated only if this occurrence emits

    Script props:

    Prop Type Default Description
    name string N/A Asset id, required, dedup key
    src string N/A src attribute
    module boolean false Sets type="module"
    defer boolean false Sets the defer attribute
    children string | (() => Awaitable<string>) N/A Inline JS, evaluated only if this occurrence emits

    Fragment files written by emitFragments carry no <style>/<script>: the shell that includes them already has the assets.