Skip to content

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 ​

ts
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. sku calls onStart once after listen succeeds. The first argument is the Express app. The second argument carries httpServer and the bound port. Use httpServer for keep-alive timeouts and to close the server on SIGTERM.

tsx
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, { httpServer, port }) => {
    httpServer.keepAliveTimeout = 20_000;
    console.log('My app started on port', port);
    process.on('SIGTERM', () => {
      httpServer.close(() => {
        process.exit(0);
      });
    });
  },
});

Commands ​

These differ from Managed Data Mode SSR and Static apps:

  • sku start-ssr — development. Uses both port and serverPort
  • sku build-ssr — production assets. Run with node ./dist/server.js (listens on serverPort)
  • 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):

tsx
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, { httpServer, port }) => {
    httpServer.keepAliveTimeout = 20_000;
    console.log('My app started on port', port);
    process.on('SIGTERM', () => {
      httpServer.close(() => {
        process.exit(0);
      });
    });
  },
});

Multi-language support ​

When you use multiple languages, the browser downloads the language as needed. That download can delay first paint. To ensure translations are available immediately, call addLanguageChunk from your render params:

jsx
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. It proxies non-asset requests to the SSR service. That pattern is similar to a production reverse proxy. It also avoids 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 ​