Server Renderer
@sigx/server-renderer renders your SignalX component tree to HTML on the server — as a string or a stream — then re-attaches reactivity in the browser by hydrating the existing DOM instead of re-creating it.
It is the base SSR package: the renderer and hydrator are deliberately
strategy-agnostic — they know nothing about islands, selective hydration, or
resumability. Those strategies layer on top through a small plugin SPI, so you
only ship what you use. For client:* selective hydration, add
@sigx/ssr-islands on top.
What you get
- Document render APIs —
renderDocument(string),renderDocumentToNodeStream(Node), andrenderDocumentToWebStream(edge) take an HTML template with an outlet and own the complete response: head injection, app shell, state serialization, and async content — no hand-splicing in your server. - Lower-level render APIs —
renderToString,renderToStream(webReadableStream),renderToNodeStream(NodeReadable), and a callback-based variant when you assemble the document yourself. - Server data loading — components fetch on the server via keyed
useData/useStream(fromsigx); their resolved values are serialized into the page and restored on hydration, so the fetch does not run twice. - Hydration —
defineApp(<App/>).use(ssrClientPlugin).hydrate('#root')re-attaches handlers and effects to server-rendered DOM, with structural mismatch self-healing. - Head management —
useHead()(fromsigx) collects title/meta/link/script from inside components; the document renderer injects them before</head>. - A plugin SPI — install packs with
app.use(plugin), plusregisterClientPlugin()— the extension points islands, resumable, andDeferstrategies build on.
The three subpaths
The package splits into three tree-shakeable entry points (all
sideEffects: false) so a browser bundle never pulls in Node code:
| Import path | Use it for |
|---|---|
@sigx/server-renderer | createSSR, renderDocument, ssrClientPlugin, renderHeadToString, shared types |
@sigx/server-renderer/server | renderDocument*, renderToString, renderToStream, renderToNodeStream — Node render APIs |
@sigx/server-renderer/client | hydrate, ssrClientPlugin, hydration internals |
useHead,useData, anduseStreamare part of coresigx, not this package — they work the same in any component and gain server behavior when this renderer drives them.
A minimal end-to-end example
1. A shared component
// src/App.tsx
import { component } from 'sigx';
export const App = component(({ signal }) => {
const count = signal(0, 'count');
return () => (
<div class="app">
<h1>Hello from SignalX SSR</h1>
<button onClick={() => count(count() + 1)}>
Count: {count()}
</button>
</div>
);
});
2. Render on the server
Give the renderer a template with an <!--ssr-outlet--> marker and it assembles
the whole document — the app shell at the outlet, head tags before </head>, and
the serialized state blob:
// src/entry-server.tsx
import { renderDocument } from '@sigx/server-renderer/server';
import { App } from './App';
const template = `<!DOCTYPE html>
<html>
<head><meta charset="utf-8" /></head>
<body>
<div id="root"><!--ssr-outlet--></div>
<script type="module" src="/src/entry-client.tsx"></script>
</body>
</html>`;
export function render() {
return renderDocument(<App />, { template });
}
3. Hydrate on the client
// src/entry-client.tsx
import { defineApp } from 'sigx';
import { ssrClientPlugin } from '@sigx/server-renderer/client';
import { App } from './App';
defineApp(<App />)
.use(ssrClientPlugin)
.hydrate('#root');
hydrate() walks the existing server DOM and attaches the click handler and
reactive effects — no DOM is re-created. If the container has no SSR content,
hydrate() falls back to a fresh client render.
Next steps
- Installation — install, the
sigxdependency, and the subpath import map. - Rendering & streaming — wire the document render APIs into Express, Fastify, or an edge runtime, plus server data with
useData/useStream. - Hydration & head — the hydration entry, head management, and the plugin SPI.
- Building a full SSR app — entry wiring, an HTTP server, and routing with
@sigx/router. - API reference — every export with signatures.
