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 />)]);