--- url: https://sigx.dev/server/packages/ssr-islands/registry-and-code-splitting/ title: Registry & code splitting description: Register island components eagerly or lazily, and split each island into its own chunk --- # Registry & code splitting
On the client, the islands runtime needs a way to find the component behind each `client:*` marker. The registry maps component names to factories — eagerly, or lazily for per-island code splitting.
## Eager registration The simplest setup: import the components and register them by name before calling `hydrateIslands()`. ```ts import { registerComponent, hydrateIslands } from '@sigx/ssr-islands'; import { Counter } from './components/Counter'; registerComponent('Counter', Counter); hydrateIslands(); ``` Register several at once with `registerComponents` — keys become the names: ```ts import { registerComponents } from '@sigx/ssr-islands'; import * as Components from './components'; registerComponents(Components); // { Counter, Chart, … } ``` The name you register **must** match the component name the server serialized into the island data. > **Trade-off:** eager registration imports every island's code up front, so it is > all in the initial bundle — simple, but no code splitting. ## Lazy registration & per-island chunks To download an island's code only when its strategy fires, register a **loader** instead of a component. The loader's `import()` runs on demand: ```ts import { registerComponentChunk } from '@sigx/ssr-islands/client'; registerComponentChunk('Counter', () => import('./Counter').then((m) => m.Counter)); ``` A `client:visible` island registered this way downloads its chunk only when it scrolls into view. > **You rarely write this by hand.** The `sigxIslandsPlugin` Vite transform > generates these `registerComponentChunk` calls automatically from your islands, > keyed by a stable file-path id. Hand-write loaders only for components the > transform doesn't cover. ### Resolution order When an island needs its component, the runtime resolves it in order: 1. **Eager registry** — already loaded via `registerComponent`. 2. **Lazy registry** — registered via `registerComponentChunk`. 3. **Direct chunk URL** — from the SSR manifest (`IslandInfo.chunkUrl`), see [Plugin setup](/server/packages/ssr-islands/plugin-setup). `loadIslandComponent(info)` runs this full resolution (with in-flight dedup); `resolveComponent(name)` checks the eager then lazy registries and caches the result for instant subsequent lookups. ## Prefetching For deferred islands, warm the browser cache early with `` so the chunk is ready before the strategy fires: ```ts import { prefetchIslandChunks, getIslandData } from '@sigx/ssr-islands'; // On DOMContentLoaded — prefetch all deferred islands' chunks prefetchIslandChunks(getIslandData()); ``` Pass a list of strategies to limit which islands are prefetched (default: all deferred strategies). ## The registry class For advanced cases, `HydrationRegistry` is the class behind the module-level helpers — useful when you want an isolated registry instance: ```ts import { HydrationRegistry } from '@sigx/ssr-islands'; import { Counter } from './components/Counter'; const registry = new HydrationRegistry() .register('Counter', Counter) .registerLazy('Chart', () => import('./Chart').then((m) => m.Chart)); await registry.resolve('Chart'); // ComponentFactory | undefined ``` ## Next steps - [Plugin setup](/server/packages/ssr-islands/plugin-setup) — manifests, options, and signal-state transfer. - [Client directives](/server/packages/ssr-islands/client-directives) — pair lazy chunks with `client:visible`. - [API reference](/server/packages/ssr-islands/api) --- url: https://sigx.dev/terminal/packages/runtime-terminal/installation/ title: Installation description: Install and configure @sigx/runtime-terminal, the Runtime Terminal package for SignalX Terminal. --- # InstallationAdd `@sigx/runtime-terminal` to your project.
## Install the package ```bash pnpm add @sigx/runtime-terminal ``` ## Verify ```tsx import * as RuntimeTerminal from '@sigx/runtime-terminal'; console.log(Object.keys(RuntimeTerminal)); ``` --- url: https://sigx.dev/server/packages/server-renderer/request-lifecycle/ title: The request lifecycle description: Set status codes and redirects from a component with useResponse, catch render failures with one error seam, and the WinterCG-clean ./node entry split --- # The request lifecycleSSR is a request/response cycle, and a component sometimes needs to shape the response — a 404, a redirect, a header — or to fail gracefully. This is the seam for that, plus the runtime split that keeps the renderer edge-portable.
## Shaping the response from a component `useResponse()` returns a recorder you call synchronously during setup — the same timing rule as `useHead`: ```tsx import { component } from 'sigx'; import { useResponse } from '@sigx/server-renderer'; const NotFound = component(() => { useResponse().status(404); return () =>One binding, one migration line, and one define — each of which
fails in a way that is hard to diagnose if you miss it.
A `client:*` directive on a component tells the islands plugin to hydrate it — and when. Components without a `client:*` directive render to static HTML and ship no client JavaScript.
The attributes need no import to type-check — installing the pack is enough. See [Installation](/server/packages/ssr-islands/installation). ## The strategies ```tsx`createFetchHandler` is the WinterCG sibling of `createRequestHandler`: a production request handler expressed in Web primitives — `(Request, platform?) => Promise
Add `@sigx/terminal-zero` to your project.
## Install the package ```bash pnpm add @sigx/terminal-zero ``` ## Verify ```tsx import * as TerminalZero from '@sigx/terminal-zero'; console.log(Object.keys(TerminalZero)); ``` --- url: https://sigx.dev/server/packages/server-renderer/installation/ title: Installation description: Install @sigx/server-renderer, its dependency, and the subpath import map --- # InstallationAdd `@sigx/server-renderer` to your project and import from the right subpath.
## Install ```bash pnpm add @sigx/server-renderer ``` ## Dependency The only runtime dependency is **`sigx`**, the host framework. It is declared as a regular dependency, so installing `@sigx/server-renderer` pulls `sigx` in automatically — there is no separate install step and no peer dependency to add. The package is **ESM-only** (`"type": "module"`, no CommonJS build). Use it from an ESM project or bundler. ## Subpath imports `@sigx/server-renderer` ships four tree-shakeable entry points (all with `sideEffects: false`). Import from the right one so a browser or edge bundle never pulls in Node code: | Import path | Use it for | Notes | |---|---|---| | `@sigx/server-renderer` | `createSSR`, `renderDocument`, `useResponse`, `ssrClientPlugin`, `renderHeadToString`, shared types | Universal, WinterCG-clean — plugin system + convenience re-exports | | `@sigx/server-renderer/server` | `renderDocument*`, `renderToString`, `renderToStream`, `renderToStreamWithCallbacks` | Web-stream render APIs; WinterCG-clean (runs on the edge) | | `@sigx/server-renderer/node` | `renderToNodeStream`, `renderDocumentToNodeStream`, `toNodeStream`, `createRequestHandler` | Node-only — anything touching `node:stream` | | `@sigx/server-renderer/client` | `hydrate`, `ssrClientPlugin`, plugin hooks | Browser-only hydration | The root and `/server` entries import **no Node built-ins**, so they run on edge runtimes. Everything that needs `node:stream` — a `Readable`, or the Node request handler — lives in `/node`. A typical app uses several across its entry files: ```tsx // entry-server.tsx import { renderDocument } from '@sigx/server-renderer/server'; // server.mjs (Node) — a Readable stream, or the request handler import { renderToNodeStream, createRequestHandler } from '@sigx/server-renderer/node'; // entry-client.tsx import { ssrClientPlugin } from '@sigx/server-renderer/client'; // any component import { useHead, useData } from 'sigx'; // head + data loading live in core sigx ``` Node stream APIs (`renderToNodeStream`, `renderDocumentToNodeStream`, `toNodeStream`) are exported **only** from `/node`. Hydration internals (`hydrate`, `hydrateNode`, `registerClientPlugin`, …) are exported **only** from `/client`. > `useHead`, `useData`, and `useStream` come from core **`sigx`**, not this > package. They are plain composables that gain server behavior when this renderer > drives them. ## Import side effects Importing the package runs one-time setup automatically: it augments the SSR directive types and patches `getSSRProps` onto built-in directives such as `show` so they serialize correctly during SSR. You do not need to call anything to enable this. ## Build / Vite wiring No special Vite config is required to **consume** the package — the standard SignalX JSX/Vite setup for your host app applies. There is **no CLI or `bin`** — the package is a library you import. ## Adding islands `@sigx/server-renderer` has no concept of `client:*` directives on its own. To hydrate only the interactive parts of a page, add the islands package on top: ```bash pnpm add @sigx/ssr-islands ``` See [`@sigx/ssr-islands`](/server/packages/ssr-islands/overview) for the full setup. ## Next steps - [Rendering & streaming](/server/packages/server-renderer/rendering) - [Hydration & head](/server/packages/server-renderer/hydration) - [API reference](/server/packages/server-renderer/api) --- url: https://sigx.dev/actors/packages/actors-surreal/installation/ title: Installation description: Install and configure @sigx/actors-surreal, the SurrealDB package for SignalX. --- # InstallationConnect, define the schema, then wire a clustered host.
## Install ```bash pnpm add @sigx/actors-surreal surrealdb ``` `surrealdb` is a peer dependency (`^2.0.8`). You need SurrealDB **≥ 3.0** running, 3.2.4 or newer recommended. ## Connect and define the schema ```ts import { Surreal } from 'surrealdb'; import { ensureSurrealSchema, surrealRetryable } from '@sigx/actors-surreal'; const db = new Surreal(); await db.connect('ws://127.0.0.1:8000', { namespace: 'app', database: 'main', authentication: { username: 'root', password: 'root' }, // REQUIRED on a connection you own — see below. retry: { enabled: true, attempts: 5, retryable: surrealRetryable }, }); await ensureSurrealSchema(db); ``` **Every replica may call this at boot, concurrently.** SurrealDB 3 has no lock primitive, so convergence is by a bounded, jittered retry that `ensureSurrealSchema()` carries **itself**, independent of the connection's `retry` setting — which matters, because the SDK ships retry disabled and this path would otherwise have none. The retry is deliberately blind to error shape; if it exhausts its attempts it verifies the tables are present before giving up, and rethrows the original error if they are not. `surrealRetryable` is unchanged by any of that and stays deliberately narrow — it is a connection-wide predicate governing your own queries, and the bootstrap does not depend on it. `ensureSurrealSchema()` SELECTs the namespace and database; it does not create them. `DEFINE NAMESPACE` / `DEFINE DATABASE` need root and are a deployment decision, so they are deliberately not issued for you. Two things in that snippet are load-bearing:surrealRetryable is not optional on a connection you pass in.
The directory claim and the storage create arm are correct because two racers
collide at commit and the loser re-runs to observe the winner. The SDK ships retry
disabled, and its built-in predicate matches a structured error code that in practice never
arrives — so without this, a lost claim race surfaces as a raw conflict error instead of
the winning entry.
[]), so the schema has
to exist before a host starts.
The renderer. Walks your component tree into ANSI lines and paints them — render modes, layered key dispatch, color-depth detection, output targets and reactive terminal size. The host platform for `@sigx/runtime-core`.
The `@sigx/terminal` umbrella re-exports this package, so most apps never install it directly — it is to the terminal what `@sigx/runtime-dom` is to the web: ```bash pnpm add @sigx/runtime-terminal ``` ## What lives here - **Render modes** — fullscreen (alternate screen), inline live regions and one-shot static rendering, plus the mount options that pick between them. See [Render modes](/terminal/docs/render-modes/). - **Layered key dispatch** — the focus model interactive components plug into. See [Input & interactive components](/terminal/docs/input-and-components/). - **Terminal capabilities** — color-depth detection, output targets and the reactive terminal size the layout engine tracks. ## Next steps See where it sits in the stack in [Architecture](/terminal/docs/architecture/), or jump to the [API reference](/terminal/packages/runtime-terminal/api). --- url: https://sigx.dev/terminal/packages/terminal-dev/installation/ title: Installation description: Install and configure @sigx/terminal-dev, the Terminal Dev package for SignalX Terminal. --- # InstallationAdd `@sigx/terminal-dev` to your project as a dev dependency.
## Install the package It runs your app during development only — it is not part of the `@sigx/terminal` umbrella and never ships with your app: ```bash pnpm add -D @sigx/terminal-dev ``` ## JSX setup `@sigx/terminal-dev` lists `@sigx/runtime-core` (`^0.14.0`) as a peer dependency and bundles Vite. Your `tsconfig.json` needs the usual SignalX JSX setup (`"jsx": "react-jsx"` and `"jsxImportSource": "@sigx/runtime-core"`, or the `@sigx/terminal` facade — see the umbrella's [Installation](/terminal/docs/installation/) guide). ## Verify ```bash pnpm exec sigx-terminal-dev --help ``` --- url: https://sigx.dev/actors/packages/actors-cloudflare/overview/ title: Overview description: Cloudflare Durable Objects as the backend for @sigx/actors — one DO per actor, and why the package is so small --- # CloudflareCloudflare already guarantees a single instance of a Durable Object globally and serializes requests to it. That is the virtual-actor contract — so the platform is the cluster.
## Installation ```bash pnpm add @sigx/actors-cloudflare ``` ## Why it is small This package needs none of the machinery [`@sigx/actors/cluster`](/actors/docs/clustering) uses to rebuild that contract: **no membership heartbeats, no activation directory, no HMAC-authenticated host-to-host mount.** There is no HMAC because a Durable Object stub is not network-reachable — holding the binding *is* the capability grant, and guards run once at the public edge. And there is no 421 retry because ref → object id is a pure function, so a mismatch is a config bug rather than a race. ## Storage ```ts const storage = durableObjectStorage(state.storage); ``` DO storage is strongly consistent and single-threaded per object, so the runtime's etag compare-and-set holds without a transaction. ## Reminders ```ts const reminders = durableObjectReminders({ storage: state.storage, alarms: state.storage, blockConcurrencyWhile: (fn) => state.blockConcurrencyWhile(fn), }); export class ActorHost { async alarm() { await reminders.onAlarm(); // fire what is due, re-arm the rest } } ``` The default `shardedReminders()` splits one table into fixed hash shards and polls it, because a host holds many actors and has to find whose reminder is due. **A DO holds exactly one**, so there is nothing to search and nothing to poll — reminders live in the object's own storage and the platform wakes it at the earliest due time. The visible consequence: an alarm fires **at** the due time, where `shardedReminders()` promises only "at or after `nextDue`". ## Client sockets Browsers can reach actors over a WebSocket here, terminating in either half: ```ts createWorkerHandler({ ..., socket: {} }); // in the Worker createWorkerHandler({ ..., socket: { terminate: 'object' } }); // in the object ``` **Worker-terminated** gives one multiplexed socket per client, reaching every actor — the right shape for a dashboard. **Object-terminated** gives one socket per actor, accepted with the hibernation API inside the object that owns it — the room pattern, and the mode where a disconnect actually releases the activation, because teardown happens locally instead of dying at the `stub.fetch` boundary. Pair either with [`socketTransport()`](/actors/packages/actors-ws/overview) on the client. The [deployment guide](/actors/docs/cloudflare-workers#client-sockets) has the choice in full, plus the hibernation contract. ## The getting-started path Most apps do not touch the two seams directly — `createHostDurableObject()` and `createWorkerHandler()` assemble them. See [Cloudflare Workers](/actors/docs/cloudflare-workers) for the full entry, the `wrangler.jsonc` and the three gotchas that are each worth their own callout. ## Next steps - [Installation](/actors/packages/actors-cloudflare/installation) — bindings and migrations. - [API reference](/actors/packages/actors-cloudflare/api) — exports. - [Cloudflare Workers](/actors/docs/cloudflare-workers) — the deployment guide. - [`@sigx/actors-ws`](/actors/packages/actors-ws/overview) — the client half of a socket. --- url: https://sigx.dev/server/packages/server-renderer/boundaries/ title: The boundary model description: How SignalX SSR decides where HTML flushes and when a component hydrates — flush and hydrate axes, SSRBoundary, resolveBoundary, and selective hydration --- # The boundary modelA boundary is a component the renderer treats specially: it decides how that component's HTML reaches the page and when the component wakes up on the client. It's the strategy-agnostic core of SignalX SSR — islands are one pack built on it.
## Two orthogonal axes Every boundary is described by two independent choices. Keeping them separate is the whole point of the model — a server-flush decision never dictates a client-hydration decision. **`flush`** — a **server** concern: how the component's HTML gets to the page. | `flush` | Meaning | |---|---| | `inline` | Await the component's async work in place, even in a streaming render. | | `stream` | Stream the HTML when there is pending async work and the render is streaming; otherwise degrade to inline. | | `skip` | Don't run setup on the server at all — emit a placeholder wrapper and let the client mount fresh. | **`hydrate`** — a **client** concern: when the component becomes interactive. | `hydrate` | Wakes on | |---|---| | `load` | As soon as the bundle loads. | | `idle` | The next idle callback. | | `visible` | Scrolled into view. | | `media` | A media query matches (see `media`). | | `interaction` | The first pointer/keyboard/touch/focus event on the element. | | `never` | Never — server HTML only, no client component. | A static marketing section might be `{ flush: 'inline', hydrate: 'never' }`; a below-the-fold widget `{ flush: 'stream', hydrate: 'visible' }`; a client-only chart `{ flush: 'skip', hydrate: 'load' }`. ## Describing a boundary `SSRBoundary` is the resolved description the renderer works from: ```ts interface SSRBoundary { id: number; // renderer-assigned, from core's component-id scheme flush: BoundaryFlush; // 'inline' | 'stream' | 'skip' hydrate: BoundaryHydrate; // 'load' | 'idle' | 'visible' | 'media' | 'interaction' | 'never' media?: string; // required when hydrate is 'media' fallback?: () => JSXElement; // server-only placeholder for 'stream' / 'skip'; never serialized chunk?: { url: string; export?: string }; props?: RecordAdd the package and its ioredis peer, then hand the providers to
the cluster() plugin.
Add `@sigx/terminal-ui` to your project.
## Install the package ```bash pnpm add @sigx/terminal-ui ``` ## Verify ```tsx import * as TerminalUI from '@sigx/terminal-ui'; console.log(Object.keys(TerminalUI)); ``` --- url: https://sigx.dev/server/packages/server-renderer/hydration/ title: Hydration & head description: Hydrate server-rendered DOM, manage the document head, and extend rendering through the plugin SPI --- # Hydration & headAfter the server sends HTML, the client re-attaches reactivity to that existing DOM. This guide covers the hydration entry point, head management, and the plugin SPI that advanced strategies build on.
## The client hydration entry The high-level entry is the `ssrClientPlugin`. It adds `hydrate()` to your `App`: ```tsx import { defineApp } from 'sigx'; import { ssrClientPlugin } from '@sigx/server-renderer/client'; import { App } from './App'; defineApp(Mount it beside ops(), give it the same secret, and point your
scraper at it.
Hand the renderer an HTML template, let it own the whole response — head, shell, state, async content — and stream it into a real HTTP server.
## The document render APIs The `renderDocument*` family takes a full HTML template containing an outlet marker and assembles the complete document: collected `useHead()` tags injected before ``, the app shell at the outlet, the serialized state blob, any streamed async chunks, and the template tail. You no longer hand-splice `template.replace('', html)` in your server. | API | Returns | Default mode | Best for | |---|---|---|---| | `renderDocument` | `Promise