Migrate from Webpack SSR
CAUTION
Experimental — not for production. Managed Data Mode SSR is available for evaluation and testing. Do not use it in production yet; the API and behaviour may change. In the meantime, continue using Webpack SSR.
High-level guide for moving from Webpack SSR (sku start-ssr / sku build-ssr / renderCallback) to Managed Data Mode SSR.
Webpack SSR was lower-level and often required bespoke app behaviour, so migration details will depend on your solution. For day-to-day API detail, prefer the Getting started topic pages.
Requirements
bundler: 'vite'andbuildType: 'ssr'- Relative
publicPathonly - Move off the config
publicassets folder — import assets from modules instead - Drop
dangerouslySetViteConfigandvitePlugins— unsupported for SSR; raise use-cases via support - Treat Jest → Vitest as a prerequisite (
testRunner: 'vitest'). Prefer a separate PR; use@sku-lib/codemod jest-to-vitest - Replace webpack
baseUrl: '.'/ baresrc/…imports with#subpath imports viapathAliases. Runpnpm dlx @sku-lib/codemod migrate-root-resolution .
Config and commands
Replace Webpack SSR scripts and dual-port config with SSR’s single-port shape:
{
"scripts": {
"start": "sku start-ssr",
"start": "sku start",
"build": "sku build-ssr",
"build": "sku build"
}
}import type { SkuConfig } from 'sku';
export default {
bundler: 'vite',
buildType: 'ssr',
publicPath: '/',
port: 3000,
serverPort: 8001,
} satisfies SkuConfig;- Export
getSitewhen more than one site - Ports: Webpack SSR used dual ports (
port+serverPort). Managed Data Mode is single-port — useport(orPORTat runtime). DropserverPort - Deploy layout:
node dist/server/server.jswith siblingclient/+server/— not webpack’s singledist/server.js - Type server-entry
middlewarefor Express 4; install React Router 8 in the app
Routes and request entries
Compose routes with path (or index) and lazy in routesEntry. Put loader, action, and Component on page modules — see Routing. Optional mapRoutePath maps one logical path to per-site concrete paths — see Multi-language.
Replace { renderCallback, middleware, onStart } with defineServerEntry / defineClientEntry — see Request entries.
Lazy page modules must export a named Component (not export default).
sku streams the Document — put isomorphic wrapping in the root layout and env-differing values in getReactContext.
Map webpack onStart({ app }) to server-entry onListen({ app, httpServer, port }) (bound port + httpServer for keep-alive timeouts).
Trust proxy is opt-in via config expressTrustProxy (sets hop count 1), not via onStart / onListen. Other trust-proxy values go in onListen via app.set('trust proxy', …).
Keep server-only construction in server getReactContext (or server-only helpers) and consume via useReactContext().
App-level providers
Wire createSkuContexts — there is no app Providers export.
Router-aware wrapping moves into your root layout route.
Vocab: getLanguage on the server entry and VocabProvider in the root layout — see Multi-language.
Braid: ensure braid-design-system/reset runs before any Braid-touching server module — see Providers → Braid reset.
Data loading and middleware
Prefer render-time data loading for page content. Use loaders for redirects, headers, or waterfalls.
Apollo: replace getDataFromTree with streaming transport over useInsertHtml — see Apollo streaming hydration.
Keep production handlers on server-entry middleware; keep local mocks in devServerMiddleware — see Middleware.
When sibling client/ is present, production mounts Node static under publicPath before server-entry middleware so catch-all URL-pattern middleware cannot eat hashed assets. Productionised deploys host those assets outside Node instead.
Never put Express req in RouterContextProvider
Prefer values both sides can supply. Raw req is bot available on client navigations — see Data loading → Router context.
CSP and hydration
Use header CSP and the single request-scoped nonce (getCspNonce / req.getCspNonce) — see CSP.
Drop hand-rolled HTML templates / getHeadTags / getBodyTags.
Hydration is full-document (hydrateRoot(document)), not a partial mount inside markup from renderCallback.
Troubleshooting
If sku start fails with React “Element type is invalid … got: object” for a CJS package that still builds in production, see CJS default-export interop.
See also
- Getting started — Managed Data Mode overview
- Request entries —
defineServerEntry/defineClientEntry - Routing — route tree and page modules
- Providers — typed hooks and Braid reset
- Data loading — render-time fetch and Apollo
- Middleware — Express vs
devServerMiddleware - Deploy to production —
dist/server+dist/client - Webpack SSR — current production path
