API reference
Every public export of @sigx/server-renderer v0.13.0, grouped by kind. The import path for each symbol is noted, since the package splits into three subpaths: @sigx/server-renderer (main), @sigx/server-renderer/server, and @sigx/server-renderer/client.
Document render functions
These take a full HTML template with an outlet marker and assemble the complete
response (head injection, app shell, state serialization, async content). State
serialization is on by default here (serializeState: false opts out) — these
are the zero-config entry points. All accept a JSX element or an App and a
DocumentOptions. Exported from the main entry and /server; also available as
methods on createSSR() (where registered plugins apply).
renderDocument
Render a complete HTML document to a string. Default mode: 'blocking' — every
keyed useData/useStream resolves inline (no placeholders or replacement
scripts), which is ideal for crawlers and AI agents. The sigx:ready completion
script is still emitted so gated hydration runs.
function renderDocument(input: JSXElement | App, options: DocumentOptions): Promise<string>
- Returns — a promise resolving to the full document HTML. Rejects if
options.signalaborts.
renderDocumentToNodeStream
Stream a complete document as a Node Readable, plus a shell promise that
settles before the first byte — await it to decide the HTTP status code, then
pipe. Default mode: 'stream'.
function renderDocumentToNodeStream(
input: JSXElement | App,
options: DocumentOptions
): { stream: import('node:stream').Readable; shell: Promise<void> }
- Returns —
{ stream, shell }. The stream is byte-mode (not object mode) so backpressure is measured in bytes.
renderDocumentToWebStream
Stream a complete document as a web ReadableStream<Uint8Array> of UTF-8 bytes,
ready as a Response body (edge runtimes, Workers, Deno). Default mode:
'stream'.
function renderDocumentToWebStream(input: JSXElement | App, options: DocumentOptions): ReadableStream<Uint8Array>
DocumentOptions
Options for the renderDocument* family. Extends SSRContextOptions.
interface DocumentOptions extends SSRContextOptions {
template: string; // full HTML with the outlet marker
outlet?: string; // default: '<!--ssr-outlet-->'
mode?: 'stream' | 'blocking'; // default: 'blocking' (string) / 'stream' (streams)
signal?: AbortSignal; // abort: stream ends early, string render rejects
onError?: (error: Error, phase: 'shell' | 'stream') => void;
serializeState?: boolean; // default: true
}
- template — the HTML document; must contain
outlet. Head tags inject before</head>; the tail splits at</body>so entry scripts flush with the shell. - mode —
'blocking'resolves all keyed async inline;'stream'flushes the shell then swaps in async chunks out of order. - onError —
'shell'fires before any byte is produced (you can still send a 500);'stream'fires mid-stream. Per-component errors stream their own fallbacks and do not reach this. - serializeState — serialize resolved
useData/useStreamvalues for hydration pickup. Setfalseto opt out.
Production fetch handler
Exported from /server and the main entry — both WinterCG-clean. See
The fetch handler for the guide.
createFetchHandler
Create a fetch-shaped production request handler — the WinterCG sibling of
createRequestHandler for Cloudflare Workers, Deno, Bun, Vercel Edge and Netlify.
Streams shell-first, switches to mode: 'blocking' for crawlers, resolves
useResponse status/redirects before the first byte.
function createFetchHandler<TPlatform = unknown>(
options: FetchHandlerOptions<TPlatform>
): FetchHandler<TPlatform>
type FetchHandler<TPlatform = unknown> =
(request: Request, platform?: TPlatform) => Promise<Response>
// `platform` becomes REQUIRED when TPlatform doesn't admit undefined
interface FetchHandlerOptions<TPlatform = unknown> {
template: string | ((url: string, request: Request, platform: TPlatform) => string | Promise<string>);
app: (url: string, request: Request, platform: TPlatform) => App | JSXElement | Promise<App | JSXElement>;
document?: Omit<DocumentOptions, 'template' | 'mode'>
| ((url: string, request: Request, platform: TPlatform) => Omit<DocumentOptions, 'template' | 'mode'>);
isBot?: (userAgent: string, request: Request) => boolean; // default: defaultIsBot
ssr?: Pick<SSRInstance, 'renderDocumentChunks'>; // default: plugin-less shared instance
}
defaultIsBot
The crawler UA regex shared by the fetch, Node and dev handlers.
function defaultIsBot(userAgent: string): boolean
chunksToBytes
Encode a chunk generator as a pull-based UTF-8 byte stream (backpressure honored;
cancellation releases the generator). The encoder under renderDocumentToWebStream
and createFetchHandler.
function chunksToBytes(chunks: AsyncGenerator<string>): ReadableStream<Uint8Array>
Render functions
renderToString
Render a JSX element or App to the app-shell HTML string — no template, head
injection, or state serialization (use renderDocument for those). Awaits all
keyed useData/useStream components and plugin streaming chunks before
resolving. Accepts an App from defineApp() (extracts the root component +
AppContext for DI) or a raw JSX element.
function renderToString(input: JSXElement | App, context?: SSRContext): Promise<string>
- input — a raw JSX element, or an
AppfromdefineApp(). - context — optional pre-built
SSRContext(auto-created if omitted). - Returns — a promise resolving to the full HTML string.
- Exported from the main entry and
/server.
renderToStream
Render to a web ReadableStream<string>. Pull-based with 4KB chunk batching and
natural backpressure; streams the shell first, then async-component replacement
scripts interleaved as they resolve, then a final completion script. Recommended
default.
function renderToStream(input: JSXElement | App, context?: SSRContext): ReadableStream<string>
- input — JSX element or
App. - context — optional
SSRContext. - Returns — a web
ReadableStream<string>. - Exported from the main entry and
/server.
renderToNodeStream
Render to a Node.js Readable stream (objectMode), bypassing WebStream overhead.
Faster than renderToStream() on Node — recommended for Express/Fastify/H3. Pipe
directly to the HTTP response.
function renderToNodeStream(input: JSXElement | App, context?: SSRContext): Readable
- input — JSX element or
App. - context — optional
SSRContext. - Returns — a Node
Readable(pipe to the response:stream.pipe(res)). - Exported from
/serveronly (not the main entry).
renderToStreamWithCallbacks
Callback-based streaming for fine-grained control. Calls onShellReady(html) once
with the full synchronous shell (including plugin-injected HTML and the completion
script), then onAsyncChunk(chunk) per resolved async component, then
onComplete(); onError(err) on failure.
function renderToStreamWithCallbacks(input: JSXElement | App, callbacks: StreamCallbacks, context?: SSRContext): Promise<void>
- input — JSX element or
App. - callbacks — a
StreamCallbacksset. - context — optional
SSRContext. - Returns — a promise that resolves when streaming completes.
- Exported from
/serveronly (not the main entry).
renderVNodeToString
Low-level helper that renders a single VNode subtree to an HTML string using an
existing SSRContext (used internally for deferred async content).
function renderVNodeToString(element: JSXElement, ctx: SSRContext, appContext?: AppContext | null): Promise<string>
- element — the VNode subtree to render.
- ctx — an existing
SSRContext. - appContext — optional
AppContextfor DI. - Returns — a promise resolving to the subtree's HTML.
- Exported from the main entry and
/server.
SSR instance & context
createSSR
Creates an SSR render pipeline. Plugins are installed on the app
(app.use(pack())) and merged in per render call — the public install path;
SSRInstance.use() was removed in 0.13. Pass a render input as a JSX element or an
App, and render complete documents with .renderDocument()/
.renderDocumentToNodeStream()/.renderDocumentToWebStream(), or a shell with the
lower-level .render()/.renderStream()/.renderNodeStream()/
.renderStreamWithCallbacks(). With no plugins the hooks are no-ops and it behaves
like the standalone functions; islands/selective/resumable hydration are layered on
as SSRPlugins. The renderDocument* methods append stateSerializationPlugin()
automatically unless it is already registered or serializeState: false is passed.
function createSSR(instanceOptions?: CreateSSROptions): SSRInstance
- instanceOptions — advanced/internal
{ plugins? }; prefer installing packs withapp.use(pack()). Instance plugins run before app-carried ones (deduped by name, first wins). - Returns — an
SSRInstance. - Exported from the main entry.
createSSRContext
Create a fresh SSRContext that tracks component IDs/markers, collected <head>
HTML, plugin data, streaming mode, and pending async components.
function createSSRContext(options?: SSRContextOptions): SSRContext
- options —
streaming,onComponentError, andappContext(seed the context from an existingAppContextsoinject()resolves during render). - Returns — a new
SSRContext. - Exported from the main entry and
/server.
SSRContext state & pack authoring (0.13)
The request-scoped SSRContext exposes a typed contract for pack authors:
// register request-scoped state to ship to the client under a key
registerSerializedState(key: string, value: unknown): void;
// the component currently rendering, and the recorded hydration boundaries
currentComponentId(): number | undefined;
boundaries(): ReadonlyMap<number, SSRBoundaryRecord>;
registerSerializedState(key, value)— publish a value into the page'swindow.__SIGX_ASYNC__blob underkey. The blob is codec-aware: top-level and nestedDate/Map/Set/bigint/URL/RegExp/ explicitundefined— plus any registered custom type handlers — round-trip, not just JSON primitives. Functions and circular references are still rejected.- A plugin can also implement the
onStreamEnd(ctx)server hook — called once after the streaming loop drains, before the completion script — to flush end-of-response state.
Packs install through a small public seam:
interface SSRPack extends SSRPlugin { install(app: App): void }
SSRPack— anSSRPluginwith aninstall(app)method;app.use(pack)calls it. Inside,provideSSRPlugin(app._context, pack)registers the plugin on the app so the render pipeline picks it up (getSSRPlugins/SSR_PLUGINS_TOKENread it back).reviveFromServer(...)— now public — revives a value serialized into the hydration blob (applying the same codec + custom handlers) on the client; it is exported from the client entry (@sigx/server-renderer/client).
Head management
useHead()itself lives in coresigx(a browser-standalone composable). During server rendering it collects configs onto the per-requestSSRContext; this package only renders them.renderDocument*injects the result automatically.
renderHeadToString
Render an array of collected HeadConfig objects to an HTML string. Dedupes meta
by name/property/http-equiv/charset, applies the last title (through
titleTemplate's %s if set), and HTML/attr-escapes output.
function renderHeadToString(configs: HeadConfig[]): string
- configs — collected
HeadConfigobjects (theHeadConfigtype is exported fromsigx). - Returns — the head HTML string.
- Exported from the main entry.
State serialization
stateSerializationPlugin
The built-in SSRPlugin (name 'sigx:state') that ships values resolved by keyed
useData/useStream to the client as window.__SIGX_ASYNC__, keyed by the
calls' explicit keys, so fetchers don't re-run after hydration. It's a plain
SSRPlugin, so opt in for the lower-level render APIs by passing it to
createSSR({ plugins: [stateSerializationPlugin()] }); the renderDocument* family
enables it by default.
function stateSerializationPlugin(): SSRPlugin
- Exported from the main entry.
serializeAsyncScript / asyncAssignmentJs
Low-level helpers behind the state plugin: serializeAsyncScript builds the
<script> that assigns the __SIGX_ASYNC__ blob; asyncAssignmentJs produces the
assignment JS for a set of key/value pairs. Both exported from /server.
function serializeAsyncScript(values: Record<string, unknown>): string
function asyncAssignmentJs(values: Record<string, unknown>): string
Streaming script helpers
generateStreamingScript
Returns the one-time client bootstrap <script> defining
window.$SIGX_REPLACE(id, html), which swaps an async placeholder
([data-async-placeholder="id"]) with rendered HTML and dispatches a
sigx:async-ready CustomEvent. Emitted once before any replacement scripts.
function generateStreamingScript(): string
- Returns — the bootstrap script HTML.
- Exported from
/server.
generateReplacementScript
Generate the <script>$SIGX_REPLACE(id, "...escaped html...")</script> for a
resolved async component, used during streaming to replace its placeholder.
function generateReplacementScript(id: number, html: string, extraScript?: string, preScript?: string): string
- id — the async component id.
- html — rendered HTML to inject.
- extraScript — optional script appended after the replacement call.
- preScript — optional script run before the replacement call (the state plugin installs per-component keys here, ahead of hydration listeners).
- Returns — the replacement script HTML.
- Exported from
/server.
generateAppendBootstrap / generateAppendScript
Progressive-text streaming helpers for useStream. generateAppendBootstrap
returns the one-time window.$SIGX_APPEND(id, text) bootstrap; generateAppendScript
returns a per-chunk append call that grows a streaming text node as tokens arrive.
function generateAppendBootstrap(): string
function generateAppendScript(id: number, text: string): string
- Exported from
/server.
escapeJsonForScript
Escape a JSON string for safe embedding inside a <script> tag (replaces <, >,
U+2028, U+2029) to prevent XSS / script-context breakout.
function escapeJsonForScript(json: string): string
- json — the JSON string to escape.
- Returns — the escaped string.
- Exported from
/server.
generateSignalKey
Generate the stable serialization key for a signal during SSR state
capture/restore. Named signals use the name; unnamed use a positional key
($0, $1, …). Server (tracking) and client (restoring) MUST both use this for
key parity.
function generateSignalKey(name: string | undefined, index: number): string
- name — the signal's name, or
undefinedfor positional keying. - index — the declaration index for unnamed signals.
- Returns — the serialization key.
- Exported from the main entry and
/server.
Client hydration
ssrClientPlugin
A sigx app Plugin that adds app.hydrate(container) to the App instance.
hydrate() resolves a string selector or Element, finds the root component +
AppContext, and hydrates existing SSR DOM (or falls back to client-side
render() when the container has no SSR content).
const ssrClientPlugin: Plugin
- Use via
defineApp(<App/>).use(ssrClientPlugin).hydrate('#root'). - Exported from the main entry and
/client.
hydrate
Core hydration entry: normalizes element to a VNode, sets the current
AppContext for DI, runs registered client plugins' beforeHydrate (return
false to skip the DOM walk, e.g. resumable SSR), walks existing SSR DOM via
hydrateNode, then runs afterHydrate hooks. Stores _vnode on the container.
function hydrate(element: any, container: Element, appContext?: AppContext): void
- Exported from
/client.
hydrateNode
Hydrate a single VNode against existing DOM (no DOM creation in the happy path);
attaches events/props/directives/refs, skips SSR comment markers
(<!--t-->, <!--$c:N-->), recovers from minor SSR drift by scanning forward
(dev warns), and returns the next sibling DOM node. Recurses for
fragments/components/elements.
function hydrateNode(vnode: VNode, dom: Node | null, parent: Node): Node | null
- Exported from
/client.
hydrateComponent
Hydrate a single component: locates its trailing <!--$c:N--> marker, builds
reactive props/slots, creates the ComponentSetupContext (with the client ssr
flags and any serverState to restore keyed useData/useStream values), runs
setup, and creates the reactive effect that hydrates-on-first-run then patches.
Used by SSR strategy plugins for selective/island hydration.
function hydrateComponent(vnode: VNode, dom: Node | null, parent: Node, serverState?: Record<string, any>, trailingMarker?: Comment | null): Node | null
- Exported from
/client.
registerClientPlugin
Register a client-side SSRPlugin so its
client.beforeHydrate/hydrateComponent/afterHydrate hooks run during
hydration. The mechanism behind selective/island/resumable hydration strategies.
function registerClientPlugin(plugin: SSRPlugin): void
- Exported from
/client.
getClientPlugins / clearClientPlugins
Return all currently registered client-side SSRPlugins, or clear them (useful in
tests).
function getClientPlugins(): SSRPlugin[]
function clearClientPlugins(): void
- Exported from
/client.
createRestoringSignal
Create a signal() replacement that restores values from server-captured state by
key (via generateSignalKey), avoiding client re-fetch during hydration.
Dev-warns once if an unnamed (positional-key) signal is restored, since
declaration-order drift between server/client builds silently mismatches state.
function createRestoringSignal(serverState: Record<string, any>): SSRSignalFn
- Exported from
/client.
setPendingServerState
Set server-captured signal state to apply to the next component mounted during hydration (used internally when mounting async components after streaming). The context extension consumes and clears it.
function setPendingServerState(state: Record<string, any> | null): void
- Exported from
/client.
getCurrentAppContext / setCurrentAppContext
Get or set the AppContext tracked during hydration (for DI in deferred/island
hydration callbacks).
function getCurrentAppContext(): AppContext | null
function setCurrentAppContext(ctx: AppContext | null): void
- Exported from
/client.
Interfaces & types
SSRInstance
The object returned by createSSR(). Plugins come from the app you render
(app.use(pack())) — there is no use() method as of 0.13. Render methods accept
SSRContextOptions or a pre-built SSRContext. createContext() builds a raw
SSRContext with this instance's plugins pre-configured.
interface SSRInstance {
// Complete-document rendering (state serialization on by default)
renderDocument(input: JSXElement | App, options: DocumentOptions): Promise<string>;
renderDocumentToNodeStream(input: JSXElement | App, options: DocumentOptions): { stream: import('node:stream').Readable; shell: Promise<void> };
renderDocumentToWebStream(input: JSXElement | App, options: DocumentOptions): ReadableStream<Uint8Array>;
// Lower-level shell rendering
render(input: JSXElement | App, options?: SSRContextOptions | SSRContext): Promise<string>;
renderStream(input: JSXElement | App, options?: SSRContextOptions | SSRContext): ReadableStream<string>;
renderNodeStream(input: JSXElement | App, options?: SSRContextOptions | SSRContext): import('node:stream').Readable;
renderStreamWithCallbacks(input: JSXElement | App, callbacks: StreamCallbacks, options?: SSRContextOptions | SSRContext): Promise<void>;
createContext(options?: SSRContextOptions): SSRContext;
}
- Exported as a type from the main entry.
SSRPlugin
The strategy-agnostic extension interface that implements
selective/island/resumable/Defer hydration without core changes. Server hooks
fire during render; client hooks fire during hydration.
interface SSRPlugin {
name: string;
server?: {
setup?(ctx: SSRContext): void;
transformComponentContext?(ctx: SSRContext, vnode: VNode, componentCtx: ComponentSetupContext): ComponentSetupContext | void;
suppressComponentRender?(id: number, vnode: VNode, ctx: SSRContext): { placeholder?: string } | void;
afterRenderComponent?(id: number, vnode: VNode, html: string, ctx: SSRContext): string | void;
handleAsyncSetup?(id: number, ssrLoads: Promise<void>[], renderFn: () => any, ctx: SSRContext): { mode: 'block' | 'stream' | 'skip'; placeholder?: string } | void;
onAsyncComponentResolved?(id: number, html: string, ctx: SSRContext): { html?: string; script?: string; preScript?: string } | void;
getInjectedHTML?(ctx: SSRContext): string | Promise<string>;
getStreamingChunks?(ctx: SSRContext): AsyncGenerator<string> | void;
};
client?: {
beforeHydrate?(container: Element): boolean | void;
transformComponentContext?(vnode: VNode, componentCtx: ComponentSetupContext): ComponentSetupContext | void;
hydrateComponent?(vnode: VNode, dom: Node | null, parent: Node): Node | null | undefined;
afterHydrate?(container: Element): void;
};
}
Server hooks, in lifecycle order around each component:
server.transformComponentContextruns after theComponentSetupContextis built but beforesetup()— mutate or replace it (swapctx.signal, adjust thessrhelper, transform props). Return a new context, or void to accept as-is.server.suppressComponentRenderruns aftertransformComponentContext(id assigned), still beforesetup()— return an object to skip the component's setup/render entirely and emit its (optional)placeholderstring followed by the<!--$c:id-->hydration marker; return void to render normally. First plugin to return an object wins. This is how islands makeclient:onlyship no server HTML, and it works in both string and streaming modes.server.afterRenderComponentreceives the rendered HTML and can transform it.server.handleAsyncSetupchoosesblock/stream/skipper async component;onAsyncComponentResolvedcan rewrite the streamed replacement (withscript/preScript);getInjectedHTMLandgetStreamingChunksemit extra markup.
Client hooks (during hydration):
client.beforeHydratereturningfalseskips the default DOM walk (resumable).client.transformComponentContextis the hydration-time mirror ofserver.transformComponentContext: it runs after the context is built and beforesetup()during hydration (noSSRContextis available then, so only the vnode and built context are passed). A strategy can swapctx.signalfor a variant that restores each signal from server-captured state — keeping render and hydration symmetric.client.hydrateComponentreturning aNodeclaims a component (islands interceptclient:*props).- Exported as a type from the main entry.
SSRContext
Per-render context tracking component IDs/markers, collected head HTML, streaming flag, pending async components, and per-plugin data (get/setPluginData keyed by plugin name).
interface SSRContext {
_componentId: number;
_componentStack: number[];
_head: string[];
_onComponentError?: (error: Error, componentName: string, componentId: number) => string | null;
_plugins?: SSRPlugin[];
_pluginData: Map<string, any>;
_streaming: boolean;
_pendingAsync: CorePendingAsync[];
nextId(): number;
pushComponent(id: number): void;
popComponent(): number | undefined;
addHead(html: string): void;
getHead(): string;
getPluginData<T>(pluginName: string): T | undefined;
setPluginData<T>(pluginName: string, data: T): void;
}
- Exported as a type from the main entry and
/server.
SSRContextOptions
Options for createSSRContext() and the render methods. streaming defaults to
true. onComponentError returns fallback HTML for a component whose setup()
throws, or null for the default error placeholder.
interface SSRContextOptions {
streaming?: boolean;
onComponentError?: (error: Error, componentName: string, componentId: number) => string | null;
}
- Exported as a type from the main entry and
/server.
RenderOptions
Optional wrapper carrying a custom SSRContext (auto-created if absent).
interface RenderOptions {
context?: SSRContext;
}
- Exported as a type from the main entry and
/server.
CorePendingAsync
A core-managed pending async component: its component id plus a promise resolving
to its rendered HTML once its keyed useData/useStream data resolves. Populated
by render-core in streaming mode when no plugin overrides.
interface CorePendingAsync {
id: number;
promise: Promise<string>;
}
- Exported as a type from the main entry and
/server.
StreamCallbacks
Callback set for renderToStreamWithCallbacks(): onShellReady(shell html),
onAsyncChunk(per resolved async component), onComplete(), onError(err).
interface StreamCallbacks {
onShellReady: (html: string) => void;
onAsyncChunk: (chunk: string) => void;
onComplete: () => void;
onError: (error: Error) => void;
}
- Exported as a type from
/server.
SSRHelper
The ssr object exposed as ctx.ssr on every ComponentSetupContext — two
environment flags for the rare branch that must know where it is running. Async
data loading lives in the useData/useStream composables (from sigx), not
here.
interface SSRHelper {
readonly isServer: boolean; // running on the server
readonly isHydrating: boolean; // hydrating server-rendered DOM on the client
}
- Exported as a type from the main entry.
HeadConfig & friends
Config objects passed to useHead()/renderHeadToString(). titleTemplate uses
%s as the title placeholder; meta is deduped by
name/property/http-equiv/charset.
interface HeadConfig {
title?: string;
titleTemplate?: string;
meta?: HeadMeta[];
link?: HeadLink[];
script?: HeadScript[];
htmlAttrs?: { lang?: string; dir?: string; [key: string]: string | undefined };
bodyAttrs?: { class?: string; [key: string]: string | undefined };
}
HeadMeta, HeadLink and HeadScript are the per-tag descriptors (boolean
script attrs like async/defer render as bare attributes; innerHTML becomes
the script body). All exported as types from the main entry.
SSRSignalFn
signal() function type augmented with an optional name used as the hydration
serialization key. Shared by server tracking signals and client restoring signals.
type SSRSignalFn = {
<T extends Primitive>(initial: T, name?: string): PrimitiveSignal<T>;
<T extends object>(initial: T, name?: string): Signal<T>;
}
- Exported as a type from the main entry and
/server(re-exported from/client).
HydrateFn
The hydrate function signature (mirrors the MountFn pattern).
type HydrateFn<TContainer = any> = (element: any, container: TContainer, appContext: AppContext) => (() => void) | void
- Exported from
/clientalongsidessrClientPlugin.
ComponentFactory / InternalVNode
ComponentFactory is the minimal component-factory shape consumed by
hydrateComponent; InternalVNode is a VNode extended with internal hydration
bookkeeping (subtree refs, reactive effect, props/slots), exposed for plugin
authors. Both exported as types from /client.
Module augmentations
The package augments @sigx/runtime-core via declare module to integrate SSR
into the core framework types. These apply automatically on import: getSSRProps
on directive definitions, hydrate() on App (via ssrClientPlugin), and the
ssr helper on ComponentSetupContext.
The
client:*directive types (client:load,client:visible, …) are not part of this package — they ship with@sigx/ssr-islandsvia its/jsxsubpath.
