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>): aTypeError, at construction - Function-valued attributes: a function has no HTML form,
on*included - Missing execution values:
Scope.geton 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
- Documentation: the rest of this site