SSR mode
The @sigx/vite plugin has an SSR mode that produces both halves of a server-rendered app — the client bundle and the server bundle — from a single vite build, and a dev-server handler that shares one module graph with your app.
This is the build-and-dev side of SSR; the runtime APIs it feeds live in @sigx/server-renderer.
Turning on SSR
Pass an ssr block to the plugin, pointing at your server entry:
// vite.config.ts
import { defineConfig } from 'vite';
import { sigx } from '@sigx/vite';
export default defineConfig({
plugins: [
sigx({
ssr: { entry: 'src/entry-server.tsx' },
}),
],
});
| Option | Default | Meaning |
|---|---|---|
entry | — (required) | Your server entry module. |
clientOutDir | 'dist/client' | Where the browser bundle + manifest are written. |
serverOutDir | 'dist/server' | Where the server bundle is written. |
One vite build now runs both environments (via Vite's builder API): the client build emits .vite/manifest.json alongside the browser assets, and the server build emits entry-server.js.
The server bundle externalizes its dependencies rather than bundling a second copy of the runtime. That matters for correctness, not just size: a bundled runtime carries its own DI token identities and would never see the app's provides. (In practice the spa-ssr server bundle drops from ~97 kB to ~25 kB.)
The islands transform
If you use islands, add sigxIslands() from the @sigx/vite/islands entry:
import { sigx } from '@sigx/vite';
import { sigxIslands } from '@sigx/vite/islands';
export default defineConfig({
plugins: [
sigx({ ssr: { entry: 'src/entry-server.tsx' } }),
sigxIslands(),
],
});
interface SigxIslandsOptions {
include?: string | string[]; // default: *.island.ts(x), islands/**/*.ts(x)
exclude?: string | string[]; // default: node_modules, dist
}
It does four things:
- Stamps
__islandIdonto each island's named component export. - Rewrites signal declarations so island state gets a stable key (see automatic state keys).
- Serves
virtual:sigx-islands, which registers a lazyimport()per island. - Emits a
sigx-islands-manifest.jsonon the client build (underclientOutDir, at.vite/sigx-islands-manifest.json), mapping island names →{ chunkUrl, exportName }. Feed that toislandsPlugin({ manifest }).
An island is a named export that is a component factory, in a file the include globs match. Names must be unique across island files — that name is the key.
The dev-server handler
For vite dev, @sigx/vite/ssr gives you a request handler that renders through Vite's module runner — so the renderer, your app, and islandsPlugin() all share one module graph and one set of DI token identities:
import { createServer } from 'vite';
import { createDevRequestHandler } from '@sigx/vite/ssr';
const vite = await createServer({ server: { middlewareMode: true } });
const handler = await createDevRequestHandler(vite, {
entry: '/src/entry-server.tsx', // Vite-root-relative
entryExport: 'createApp', // default
});
app.use(vite.middlewares);
app.use(handler);
It fails fast at startup if @sigx/server-renderer isn't installed, and applies ssrFixStacktrace so errors point at your source.
For production, the counterpart is createRequestHandler from @sigx/server-renderer/node, fed your built entry-server.js and manifest — see Building a full SSR app.
Where to go next
- The boundary model — what the transform's output feeds
- Request lifecycle —
createRequestHandler,useResponse, the error seam - Islands plugin setup — consuming the islands manifest
