Skip to content
Vincle

Loading…

    renderToString

    Renders a JSX tree into an HTML string.

    Signature#

    declare function renderToString(node: unknown): Promise<string>;

    node is a single JSX node: a component, an element, a fragment (<></> for several siblings), or any Renderable. It is typed unknown because JSX's own typing already constrains what compiles.

    Usage#

    import { renderToString } from "@vincle/core";
    const Page = ({ title }: { title: string }) => (
    <html>
    <head>
    <title>{title}</title>
    </head>
    <body>
    <h1>{title}</h1>
    </body>
    </html>
    );
    const html = await renderToString(<Page title="My Site" />);

    Async components#

    Any component in the tree can be async: the component that needs data fetches it, and the returned promise resolves once the whole tree has rendered.

    import { renderToString } from "@vincle/core";
    declare const db: {
    posts: {
    findAll(opts: { limit: number }): Promise<{ id: number; slug: string; title: string }[]>;
    };
    };
    const Feed = async () => {
    const posts = await db.posts.findAll({ limit: 10 });
    return (
    <ul>
    {posts.map((p) => (
    <li key={p.id}>
    <a href={"/posts/" + p.slug}>{p.title}</a>
    </li>
    ))}
    </ul>
    );
    };
    const html = await renderToString(<Feed />);

    Components run one at a time, in document order, never in parallel, including in arrays, iterables and precompiled templates. So two siblings touching Scope never race, and when one throws or rejects, the ones after it are not invoked. A promise you created before rendering is already running; for concurrent regions, use Defer and Slot from @vincle/flow.

    Concurrent renders#

    Concurrent calls are safe, even when components use Providers or Scope.with: each render keeps its own values, with no cross-request leakage.

    const [pageA, pageB] = await Promise.all([renderToString(<PageA />), renderToString(<PageB />)]);