Server/Packages/Server Renderer
@sigx/server-renderer · Stable

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.

v0.13.0 ESM-only MIT

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 APIsrenderDocument (string), renderDocumentToNodeStream (Node), and renderDocumentToWebStream (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 APIsrenderToString, renderToStream (web ReadableStream), renderToNodeStream (Node Readable), and a callback-based variant when you assemble the document yourself.
  • Server data loading — components fetch on the server via keyed useData / useStream (from sigx); their resolved values are serialized into the page and restored on hydration, so the fetch does not run twice.
  • HydrationdefineApp(<App/>).use(ssrClientPlugin).hydrate('#root') re-attaches handlers and effects to server-rendered DOM, with structural mismatch self-healing.
  • Head managementuseHead() (from sigx) collects title/meta/link/script from inside components; the document renderer injects them before </head>.
  • A plugin SPI — install packs with app.use(plugin), plus registerClientPlugin() — the extension points islands, resumable, and Defer strategies 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 pathUse it for
@sigx/server-renderercreateSSR, renderDocument, ssrClientPlugin, renderHeadToString, shared types
@sigx/server-renderer/serverrenderDocument*, renderToString, renderToStream, renderToNodeStream — Node render APIs
@sigx/server-renderer/clienthydrate, ssrClientPlugin, hydration internals

useHead, useData, and useStream are part of core sigx, 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#

TSX
// 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:

TSX
// 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#

TSX
// 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 sigx dependency, 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.