Skip to content
Vincle

Loading…

    FAQ

    What is Vincle and when should I use it?#

    Vincle is a JSX-to-HTML renderer designed exclusively for server-side use. Use it whenever the output you want is an HTML string: static sites, email templates, SSR responses, HTML fragments from an API, PDF sources.

    Vincle is not for client-side rendering or hydration. For interactive components in the browser, pair it with HTMX, Turbo, Alpine.js, or a dedicated frontend framework.

    How is Vincle different from React?#

    React's renderers carry a virtual DOM, a reconciler, and a hydration contract. Vincle is built exclusively for server-side HTML generation:

    Vincle react-dom/server
    Dependencies 0 React runtime
    Async Native await Suspense-only
    Virtual DOM None Full
    Client runtime None Hydration
    Security Escape + URL allowlist Escape + javascript: URLs neutralized

    Can I use Vincle with React components?#

    No. Vincle uses its own JSX runtime and does not support React hooks, refs, or the React ecosystem. The syntax is the same: the runtime is incompatible.

    If you need React ecosystem libraries (MUI, Radix, React Router, TanStack), use React's own server renderer. See Coming from React to port your own components.

    What runtimes does Vincle support?#

    Node.js 22+, Bun, Deno, and Cloudflare Workers (with the nodejs_compat flag). No DOM shim, no polyfill, no browser build step.

    How do I handle async data fetching?#

    Any component can be async: the component that needs data fetches it, with no top-level loader and no prop-drilling. renderToString awaits every async component, siblings in document order, one at a time, and resolves when the whole tree has rendered.

    async function Page({ id }: { id: string }) {
    const user = await db.users.find(id);
    const posts = await db.posts.findByUser(id);
    return (
    <html>
    <body>
    <h1>{user.name}</h1>
    {posts.map((post) => (
    <Post key={post.id} {...post} />
    ))}
    </body>
    </html>
    );
    }
    const html = await renderToString(<Page id="42" />);

    How does security work in Vincle?#

    Security is the default, not an opt-in. Every value is escaped for the place it lands, and a URL whose scheme is off the allowlist becomes #blocked. What you write yourself, such as an on* handler or a style string, is escaped but not inspected. raw() is the one way to vouch for trusted HTML, rawUrl() for a URL scheme. See the Security model.

    Can I use Vincle for streaming HTML?#

    Yes. The @vincle/flow package adds deferred fragments and streaming. The shell is sent immediately while slow components render concurrently. Five adapters let you choose your client mechanism:

    • NativeAdapter: WICG spec with a small polyfill
    • TurboAdapter: Hotwire Turbo Streams
    • HtmxAdapter: HTMX OOB swaps
    • WebPlatformAdapter: native browser support, no JS
    • EsiAdapter: CDN-level ESI composition, static generation only

    Can I catch rendering errors without crashing the whole page?#

    Errors throw or reject bare: @vincle/core has no component-level boundary. Wrap renderToString in try/catch for a page-level fallback. When streaming, @vincle/flow's onError lets one fragment fail without the rest of the page:

    serve(req, () => <Page />, NativeAdapter, {
    onError: (error, { id }) => {
    console.error(`Fragment ${id} failed:`, error);
    return <p>This section failed to load.</p>;
    },
    });

    See the Error handling API page for details.

    How do I debug rendering issues?#

    TypeScript catches type errors at build time. At runtime, Vincle throws on anything that has no valid HTML form or no value:

    • Invalid tag names and children on a void element (<img>{caption}</img>): a TypeError, at construction
    • Function-valued attributes: a function has no HTML form, on* included
    • Missing execution values: Scope.get on a key that was never set

    A URL off the allowlist is not an error: it renders as #blocked.

    What is the bundle size?#

    Package Download (gzip) Dependencies
    @vincle/core < 100 KB 0
    @vincle/flow < 30 KB 0 (depends on core)

    That is the whole install: runtime, and types for every HTML and SVG element and every CSS property. csstype alone, one of the two packages React needs just to type a style prop, is 138 KB, and @types/react another 78 KB on top.

    Where can I get help?#