React, Preact & Solid
bext provides SSR adapters that let you render React, Preact, or Solid components to HTML strings inside your template sites. This lets you use existing component libraries alongside bext's built-in JSX without rewriting them.
Each adapter wraps the framework's synchronous renderToString function for use inside bext's render worker pool.
React adapter
The React adapter renders React components to HTML strings using react-dom/server.
Setup
Add React to your project dependencies:
bun add react react-dom
bun add -d @types/react @types/react-dom
Usage
// A standard React component
function PriceChart({ data }: { data: number[] }) {
return (
<div className="chart">
{data.map((value, i) => (
<div
key={i}
className="bar"
style={{ height: `${value}%`, width: '20px', background: '#06b6d4' }}
/>
))}
</div>
);
}
// Render to HTML string
const chartHtml = renderReact(PriceChart, { data: [20, 45, 80, 35, 60] });
Embedding in bext JSX templates
Use dangerouslySetInnerHTML to embed React output in your bext JSX layout:
// server/template.tsx — bext JSX template
function ProductPage({ product }: { product: Product }) {
return (
<div className="product-detail">
<h1>{product.name}</h1>
<p>{product.description}</p>
{/* React component rendered to HTML */}
<div dangerouslySetInnerHTML={{
__html: renderReact(PriceChart, { data: product.priceHistory })
}} />
</div>
);
}
API
/** Render a React component to an HTML string. */
function renderReact(component: any, props?: Record<string, any>): string;
/** Render an already-created React element to a string. */
function renderReactElement(element: any): string;
Preact adapter
Preact is a 3KB alternative to React with the same API. Use it when you need React-compatible components without the 7.5MB bundle overhead.
Setup
bun add preact preact-render-to-string
Usage
// Using Preact's h() function
function Badge({ text, color }: { text: string; color: string }) {
return h("span", {
class: "badge",
style: `background:${color}; padding:2px 8px; border-radius:4px; color:white; font-size:12px`
}, text);
}
// Render a component with props
const badgeHtml = renderPreactComponent(Badge, { text: "New", color: "#10b981" });
// <span class="badge" style="background:#10b981; padding:2px 8px; border-radius:4px; color:white; font-size:12px">New</span>
// Or render a VNode directly
const html = renderPreact(h(Badge, { text: "Sale", color: "#f43f5e" }));
With Preact JSX
If you configure jsxImportSource: "preact" in a separate tsconfig for your Preact components, you can use JSX syntax:
// components/Counter.tsx (compiled with jsxImportSource: "preact")
export function Counter({ initial }: { initial: number }) {
return (
<div class="counter">
<button>-</button>
<span>{initial}</span>
<button>+</button>
</div>
);
}
// In your bext template
const counterHtml = renderPreactComponent(Counter, { initial: 0 });
API
/** Render a Preact VNode to an HTML string. */
function renderPreact(vnode: VNode): string;
/** Render a Preact component with props to an HTML string. */
function renderPreactComponent(component: any, props?: Record<string, any>): string;
Solid adapter
The Solid adapter wraps Solid's renderToString for synchronous SSR.
Setup
bun add solid-js
Usage
function Greeting({ name }: { name: string }) {
return <h1>Hello, {name}!</h1>;
}
const html = renderSolid(() => );
// <h1>Hello, World!</h1>
API
/**
* Render a Solid component tree to an HTML string.
* Pass a function that returns the component (Solid's rendering convention).
*/
function renderSolid(fn: () => any): string;
Note: Solid is lazily loaded at runtime. If
solid-jsis not in your dependencies,renderSolid()throws a clear error message.
Mixing frameworks in one page
One of the key advantages of bext's architecture is that you can use different frameworks for different parts of the same page. The bext JSX layout renders the page shell, and React/Preact/Solid render individual components:
// server/template.tsx
export function renderPage(page: Page, ctx: RenderContext): string {
return (
<html lang="en">
<head>
<title>{page.title}</title>
<link rel="stylesheet" href="/styles.css" />
</head>
<body>
{/* bext JSX — zero overhead */}
<nav className="topnav">
<a href="/">Home</a>
<a href="/products">Products</a>
</nav>
<main>
{/* Preact component — 3KB, lightweight */}
<div dangerouslySetInnerHTML={{
__html: renderPreactComponent(SearchBox, {
placeholder: "Search products..."
})
}} />
{/* Page content */}
<div dangerouslySetInnerHTML={{ __html: page.html }} />
{/* React component — for complex data grids */}
<div dangerouslySetInnerHTML={{
__html: renderReact(DataTable, {
columns: ["Name", "Price", "Stock"],
endpoint: "/api/products"
})
}} />
</main>
{/* bext JSX footer — zero overhead */}
<footer>© 2026 My Store</footer>
</body>
</html>
);
}
When to use each
| Framework | Bundle size | Best for |
|---|---|---|
| bext JSX | 0 KB | Static layouts, navbars, footers, cards, page shells |
| Preact | ~3 KB | Lightweight interactive components, drop-in React replacement |
| React | ~140 KB | Complex components with hooks, existing React libraries |
| Solid | ~7 KB | Fine-grained reactive components, high-performance UIs |
General rule: Use bext JSX for everything that does not need client-side interactivity. Only reach for React/Preact/Solid when you have existing components or need their specific features for SSR.
Decision guide
1. Static content (text, images, navigation) -- use bext JSX 2. Server-rendered data display (tables, charts, lists) -- use Preact (small overhead) or React (if you already have the components) 3. Interactive client components (forms, modals, real-time widgets) -- use the Islands pattern with React or Preact for client hydration 4. Reactive data-driven UIs -- use Solid for fine-grained reactivity
Performance notes
All three adapters use synchronous renderToString, which runs inside bext's render worker pool. Rendering is single-threaded per worker but parallelized across the pool (default: 4 workers).
Typical SSR render times:
| Approach | Render time |
|---|---|
| bext JSX (string concat) | 20-80 us |
| Preact renderToString | 200-500 us |
| React renderToString | 500 us - 5 ms |
| Solid renderToString | 100-400 us |
For pages that are mostly static with one or two embedded framework components, the overhead is negligible because bext's ISR cache stores the rendered HTML and serves subsequent requests from cache.