---
title: FAQ
---

<script type="application/ld+json">
  {JSON.stringify({
    "@context": "https://schema.org",
    "@type": "FAQPage",
    mainEntity: [
      {
        "@type": "Question",
        name: "What is Vincle and when should I use it?",
        acceptedAnswer: {
          "@type": "Answer",
          text: "Vincle is a JSX-to-HTML renderer designed exclusively for server-side use. You should use it whenever the output you want is an HTML string: static sites, email templates, SSR responses, API HTML fragments, PDF sources. It is not for client-side rendering or hydration.",
        },
      },
      {
        "@type": "Question",
        name: "How is Vincle different from React?",
        acceptedAnswer: {
          "@type": "Answer",
          text: "React ships a virtual DOM, a reconciler, and a hydration contract, none of which help when the only output is a string. Vincle is built exclusively for server-side HTML generation. It has one package with zero dependencies, no virtual DOM, async-native rendering, security by default, and no client runtime.",
        },
      },
      {
        "@type": "Question",
        name: "Can I use Vincle with React components?",
        acceptedAnswer: {
          "@type": "Answer",
          text: "No. Vincle uses its own JSX runtime and does not support React hooks, refs, or the React ecosystem. The JSX syntax is the same, but the runtime is incompatible. If you need React ecosystem libraries (MUI, Radix, React Router), use React's own server renderer.",
        },
      },
      {
        "@type": "Question",
        name: "Does Vincle work with TypeScript?",
        acceptedAnswer: {
          "@type": "Answer",
          text: "Yes. Vincle is written in TypeScript and provides full type inference. Set jsxImportSource to '@vincle/core' in your tsconfig.json and JSX expressions are fully typed, per element: every HTML and SVG attribute is checked, so a typo is a compile error.",
        },
      },
      {
        "@type": "Question",
        name: "What runtimes does Vincle support?",
        acceptedAnswer: {
          "@type": "Answer",
          text: "Node.js 22+, Bun, Deno, and Cloudflare Workers (with the nodejs_compat flag). No DOM shim, no polyfill, no browser build step.",
        },
      },
      {
        "@type": "Question",
        name: "How do I handle async data fetching in Vincle?",
        acceptedAnswer: {
          "@type": "Answer",
          text: "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.",
        },
      },
      {
        "@type": "Question",
        name: "How does security work in Vincle?",
        acceptedAnswer: {
          "@type": "Answer",
          text: "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.",
        },
      },
      {
        "@type": "Question",
        name: "Can I use Vincle for streaming HTML?",
        acceptedAnswer: {
          "@type": "Answer",
          text: "Yes, with the @vincle/flow package. It adds deferred fragments, streaming, and DOM patching: the shell is sent immediately while heavy fragments render concurrently. Adapters are available for Turbo Streams, HTMX, the WICG Native API, and ESI-based CDN composition.",
        },
      },
      {
        "@type": "Question",
        name: "What is the bundle size of Vincle?",
        acceptedAnswer: {
          "@type": "Answer",
          text: "@vincle/core is one package with zero dependencies, under 100 KB to download, and it carries the complete per-element attribute table: every HTML and SVG element, 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. The @vincle/flow package for streaming and fragments adds under 30 KB.",
        },
      },
      {
        "@type": "Question",
        name: "Can I catch rendering errors without crashing the whole page?",
        acceptedAnswer: {
          "@type": "Answer",
          text: "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.",
        },
      },
      {
        "@type": "Question",
        name: "How do I debug rendering issues?",
        acceptedAnswer: {
          "@type": "Answer",
          text: "TypeScript catches type errors at build time. At runtime, Vincle throws on anything that has no valid HTML form or no value: an invalid tag name, children on a void element, a function-valued attribute, a Scope key that was never set. A URL off the allowlist is not an error: it renders as #blocked.",
        },
      },
    ],
  })}
</script>

# 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](/guide/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.

```tsx
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](/guide/security).

## Can I use Vincle for streaming HTML?

Yes. The [`@vincle/flow`](/integration/overview) 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:

```tsx
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](/api/core/error-handling) 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?

- **GitHub Issues**: [github.com/cjean-fr/vincle/issues](https://github.com/cjean-fr/vincle/issues)
- **Documentation**: the rest of this site
