Webpack SSR
Webpack SSR
This page describes sku’s older method for server-side rendering using Webpack. For the new experimental Managed Data Mode SSR, see Getting started.
Webpack SSR uses a low-level API and custom -ssr commands (sku start-ssr / sku build-ssr). You supply a serverEntry whose default export includes renderCallback.
Minimal setup
import type { SkuConfig } from 'sku';
export default {
clientEntry: 'src/client.tsx',
serverEntry: 'src/server/server.tsx',
public: 'src/public',
publicPath: '/',
target: 'dist',
port: 3300,
serverPort: 3301,
} satisfies SkuConfig;Sku provides an Express server. The serverEntry default export may provide renderCallback, optional middleware, and optional onStart:
import type { Server } from 'sku';
import middleware from './middleware';
import template from './template';
export default (): Server => ({
renderCallback: ({ SkuProvider, getBodyTags, getHeadTags }, req, res) => {
const app = renderToString(
<SkuProvider>
<App />
</SkuProvider>,
);
res.send(
template({ headTags: getHeadTags(), bodyTags: getBodyTags(), app }),
);
},
middleware,
onStart: (app) => {
console.log('My app started');
app.keepAliveTimeout = 20_000;
},
});Commands
These differ from Managed Data Mode SSR and Static apps:
sku start-ssr— development; uses bothportandserverPortsku build-ssr— production assets; run withnode ./dist/server.js(listens onserverPort)sku test— tests
Multi-part response
To return HTML at different times in the request, use flushHeadTags for head tags added since the previous call (typically from dynamic chunks):
import type { Server } from 'sku';
import { followupResponseTemplate, initialResponseTemplate } from './template';
import middleware from './middleware';
export default (): Server => ({
renderCallback: async (
{ SkuProvider, getBodyTags, flushHeadTags },
req,
res,
) => {
res.status(200);
// Call `flushHeadTags` early to retrieve whatever tags are available.
res.write(initialResponseTemplate({ headTags: flushHeadTags() }));
await Promise.resolve();
const app = renderToString(
<SkuProvider>
<App />
</SkuProvider>,
);
res.write(
// Call `flushHeadTags` again just in case new tags are available.
followupResponseTemplate({
headTags: flushHeadTags(),
bodyTags: getBodyTags(),
app,
}),
);
res.end();
},
middleware,
onStart: (app) => {
console.log('My app started');
app.keepAliveTimeout = 20_000;
},
});Multi-language support
When using multiple languages the browser will download the language as needed, which can delay first paint. To ensure translations are available immediately, call addLanguageChunk from your render params:
export async function serverRender({ SkuProvider, addLanguageChunk, appPath }) {
const language = getLanguageFromPath(appPath);
addLanguageChunk(language);
return renderToString(
<SkuProvider>
<StaticRouter location={appPath}>
<VocabProvider language={language}>
<App />
</VocabProvider>
</StaticRouter>
</SkuProvider>,
);
}Static rendering registers language chunks automatically. Managed Data Mode SSR uses server-entry getLanguage instead — see Multi-language.
Development server entrypoint
On the Webpack SSR path, sku start-ssr starts two services:
- A dev server for static assets
- An SSR service running your app’s server code
The dev server is the single entrypoint and proxies non-asset requests to the SSR service (similar to a production reverse proxy, and avoiding CORS for client requests). Managed Data Mode SSR uses a single port instead.
To proxy other traffic (for example APIs), use Dev Server Middleware.
See also
- Migrate from Webpack SSR — move to Managed Data Mode
- Getting started — Managed Data Mode overview
- Extra features —
devServerMiddleware
