Server-Side Rendering
The SSR bridge renders React components to HTML on the server by calling Bun as a subprocess. This lets you use React for complex UI components while keeping the rest of your application in Zig. The rendered HTML is returned as a string that you can embed in your templates or send directly as a response.
Prerequisites
Section titled “Prerequisites”- Bun installed and available on
PATH - React and ReactDOM (
bun add react react-dom)
Quick setup
Section titled “Quick setup”-
Generate SSR starter files
Terminal window pidgn assets setup --ssrThis creates the standard asset files plus an SSR worker script at
assets/ssr-worker.jsand a sample component atassets/components/App.jsx. -
Install dependencies
Terminal window bun install -
Initialize the SSR pool in your application
const pidgn = @import("pidgn");var ssr_pool = pidgn.SsrPool.init(allocator, .{.worker_script = "assets/ssr-worker.js",.pool_size = 4,.timeout_ms = 5000,});defer ssr_pool.deinit();
SsrConfig
Section titled “SsrConfig”| Option | Type | Default | Description |
|---|---|---|---|
worker_script | []const u8 | "assets/ssr-worker.js" | Path to the Bun worker script that renders components. |
pool_size | u8 | 4 | Number of worker slots in the pool (max 8). |
timeout_ms | u32 | 5000 | Maximum time in milliseconds to wait for a render call. |
Rendering a component
Section titled “Rendering a component”Call render with the component name and a JSON string of props:
fn ssrHandler(ctx: *pidgn.Context) !void { const html = try ssr_pool.render("App", "{\"title\":\"Hello\",\"message\":\"From SSR!\"}"); defer ctx.allocator.free(html);
ctx.html(.ok, html);}The component name maps to a file under assets/components/. For example, "App" loads assets/components/App.jsx.
How it works
Section titled “How it works”renderspawns a Bun subprocess:bun run <worker_script>.- The worker script receives a JSON request via stdin:
{"component":"App","props":{"title":"Hello"}}. - The worker loads the component from
assets/components/<name>.jsx, callsrenderToStringfromreact-dom/server, and writes the HTML to stdout. - The pool reads stdout and returns the HTML string to your handler.
Each render call spawns a one-shot subprocess. The pool manages round-robin scheduling across pool_size worker slots.
Writing JSX components
Section titled “Writing JSX components”Place your components in assets/components/:
const React = require("react");
function App({ title, message }) { return ( <div className="app"> <h1>{title}</h1> <p>{message}</p> </div> );}
module.exports = App;Components are loaded with require(), so use CommonJS exports (module.exports). The default export or the module itself is used as the component.
The worker script
Section titled “The worker script”The pidgn assets setup --ssr command generates a worker script like this:
const { renderToString } = require("react-dom/server");const { createElement } = require("react");
const input = JSON.parse(require("fs").readFileSync("/dev/stdin", "utf8"));const mod = require("./components/" + input.component + ".jsx");const Component = mod.default || mod;const html = renderToString(createElement(Component, input.props));process.stdout.write(html);You can customize this script for your needs — for example, adding a CSS-in-JS provider or a data fetching layer.
Limitations
Section titled “Limitations”- Each render spawns a Bun subprocess, so SSR adds latency compared to pure Zig template rendering. Use it for components that benefit from React’s component model.
- Maximum output size is 64KB per render call.
- Maximum request size (component name + props JSON) is 4KB.
- Bun must be installed on the server. If Bun is not available,
renderreturns an error. - The pool size is capped at 8 workers.
Next steps
Section titled “Next steps”- Asset Pipeline — bundle and fingerprint client-side assets
- Assets CLI —
pidgn assets setup --ssrand build commands - Templates — pidgn’s built-in template engine for simpler views
- Controllers — wire SSR into your route handlers
- Context — response helpers like
ctx.html()