Deploy adapters
ssr.adapter is the plugin's deployment seam: a plain object that shapes how the server bundle is built — externalized for Node, fully bundled for the edge — and generates whatever your platform needs around it. The adapter packages for Cloudflare, Vercel and Netlify ride this contract; so can yours.
The adapter option
import { defineConfig } from 'vite';
import sigx from '@sigx/vite';
import { cloudflare } from '@sigx/cloudflare';
export default defineConfig({
plugins: [
sigx({
ssr: {
entry: 'src/entry-server.tsx',
adapter: cloudflare(),
},
}),
],
});
The default — no adapter — is nodeAdapter(): today's externalized Node
output, byte-identical to pre-adapter builds. Only an explicit adapter changes
the build. Adapters cannot be combined with library mode.
The SigxAdapter contract
An adapter is a plain object, not a second Vite plugin — the sigx plugin stays the one authority over the ssr environment:
interface SigxAdapter {
name: string; // 'node', 'cloudflare', …
serverBuild: 'external' | 'bundled';
conditions?: string[]; // e.g. ['workerd', 'worker']
runtimeExternal?: (string | RegExp)[]; // kept as runtime imports when bundled
entry?: string; // platform entry; default: ssr.entry
target?: string; // default 'esnext' for 'bundled'
setup?(ctx: AdapterSetupContext): void | Promise<void>;
generate?(ctx: AdapterGenerateContext): void | Promise<void>;
dev?(server: ViteDevServer): void | Promise<void>;
}
serverBuildis deliberately binary.'external'resolves dependencies fromnode_modulesat runtime (Node and Bun hosts).'bundled'produces a fully self-contained server build — the ssr environment'sresolve.noExternal: true(the environments-API home of the classicssr.noExternal), your platformconditionsreplacing the Node defaults, targetesnext. The partially-external middle ground is unrepresentable on purpose: it is the dangerous zone for DI-token identity, while a total bundle lands the entry, strategy packs, handlers and registry in one module graph.runtimeExternalkeeps platform builtins as runtime imports in a bundled build —/^cloudflare:/,/^jsr:/.setupruns before any environment builds and scaffolds build inputs — most importantly the platform entry — iff absent: written once when missing, never touched again. The file is user-owned from that moment.generateruns after both environments have written: copy statics, emit platform config, validate. This is where.vercel/outputassembly lives.devhooks the Vite dev server for platform-binding proxies (howcloudflare({ devProxy: true })boots wrangler's local bindings).
The platform entry
Each adapter scaffolds src/entry.<platform>.ts on first build and never
overwrites it. Its composition order is your app's routing policy —
static assets → server functions → document render
— and every entry exports the portable { fetch } shape around
createFetchHandler. No sigx
handler serves static files; the static tier is platform-native and sits in
front.
virtual:sigx-app
A production fetch handler needs the document-side artifacts — with no
filesystem in the output. virtual:sigx-app inlines the client build's outputs
as literals (also materialized as dist/server/sigx-app.js, so a Node
server.mjs collapses four readFiles into one import):
import { template, assets, assetsFor, manifest, islandsManifest, resumeManifest }
from 'virtual:sigx-app';
| Export | What it is |
|---|---|
template | the built index.html with the outlet marker |
assets | precomputed collectAssets() preloads for every client entry |
assetsFor(entries, base?) | per-route preloads — collectAssets over the inlined manifest, with its body baked in; base defaults to the build's config.base |
manifest | the client .vite/manifest.json |
islandsManifest | the islands pack manifest, or undefined |
resumeManifest | the resume pack manifest, or undefined |
Per-route preloads come from assetsFor, so the module still imports nothing
on any platform:
import { assetsFor } from 'virtual:sigx-app';
document: (url) => ({ assets: assetsFor(ROUTE_MODULES[url] ?? []) })
What a deployed server imports
A deployed server imports its runtime dependencies (sigx, the server
packs it uses) and the modules the build emitted — never @sigx/vite,
which is a build tool and a devDependency. Manifest-derived data reaches
production only through virtual:sigx-app / dist/server/sigx-app.js, so a
server installed with npm ci --omit=dev or copied into a slim Docker layer
boots the same as one with dev dependencies present.
@sigx/vite/assets stays as the lower-level escape hatch for build-time and
dev tooling — collectAssets over a manifest you hold yourself. It imports
nothing at all (no node: builtins), so it runs on workerd, Deno and Bun. Keep
@sigx/vite/ssr for the dev half (createDevRequestHandler), which is
Node-only by nature.
virtual:sigx-app is a build artifact: importing it under vite dev throws, pointing you at
createDevRequestHandler (dev resolves template and assets live). The narrower
sibling virtual:sigx-manifests exports just the two pack manifests and resolves
in every mode — that's the one your entry-server app factory imports to
construct its packs without threading manifests through server wiring.
Where to go next
- Deploying SignalX — the platform matrix: Node, Cloudflare, Deno, Bun, Vercel, Netlify
- The fetch handler — the WinterCG runtime the adapters wire up
- Vite SSR mode — entries, the islands transform and the dev handler
