Server-Side Rendering (SSR)

This chapter introduces how to implement SSR functionality using Rsbuild.

Please note that Rsbuild itself does not provide out-of-the-box SSR functionality, but instead provides low-level APIs and configurations to allow framework developers to implement SSR. If you require out-of-the-box SSR support, you may consider using full-stack frameworks based on Rsbuild, such as Modern.js.

What is SSR

SSR stands for "Server-Side Rendering". It means that the HTML of the web page is generated by the server and sent to the client, rather than sending only an empty HTML shell and relying on JavaScript to generate the page content.

In traditional client-side rendering, the server sends an empty HTML shell and some JavaScript scripts to the client, and then fetching data from the server's API and fills the page with dynamic content. This leads to slow initial page loading times and is not conducive to user experience and SEO.

With SSR, the server generates HTML that already contains dynamic content and sends it to the client. This makes the initial page loading faster and more SEO-friendly, as search engines can crawl the rendered page.

File Structure

A typical SSR application will have the following files:

- index.html - server.js # main application server - src/ - App.js # exports app code - index.client.js # client entry, mounts the app to a DOM element - index.server.js # server entry, renders the app using the framework's SSR API

The index.html will need to include a placeholder where the server-rendered markup should be injected:

<div id="root"><!--app-content--></div>

Create SSR configuration

In the SSR scenario, two types of outputs (web and node targets), need to be generated at the same time, for client-side rendering (CSR) and server-side rendering (SSR) respectively.

At this time, you can use Rsbuild's multi-environment builds capability, define the following configuration:

rsbuild.config.ts
export default {
  environments: {
    // Configure the web environment for browsers
    web: {
      source: {
        entry: {
          index: './src/index.client.js',
        },
      },
      output: {
        // Use 'web' target for the browser outputs
        target: 'web',
      },
      html: {
        // Custom HTML template
        template: './index.html',
      },
    },
    // Configure the node environment for SSR
    node: {
      source: {
        entry: {
          index: './src/index.server.js',
        },
      },
      output: {
        // Use 'node' target for the Node.js outputs
        target: 'node',
      },
    },
  },
};

Custom server

Rsbuild does not have built-in SSR rendering capabilities, but you can implement SSR rendering through Rsbuild's custom server and environment API:

server.mjs
import express from 'express';
import { createRsbuild, loadConfig } from '@rsbuild/core';

// Implement SSR rendering function
const serverRender = (serverAPI) => async (_req, res) => {
  // Load SSR bundle
  const indexModule = await serverAPI.environments.ssr.loadBundle('index');

  const markup = indexModule.render();

  const template = await serverAPI.environments.web.getTransformedHtml('index');

  // Insert SSR rendering content into HTML template
  const html = template.replace('<!--app-content-->', markup);

  res.writeHead(200, {
    'Content-Type': 'text/html',
  });
  res.end(html);
};

// Custom server
async function startDevServer() {
  const { content } = await loadConfig({});

  const rsbuild = await createRsbuild({
    rsbuildConfig: content,
  });

  const app = express();

  const rsbuildServer = await rsbuild.createDevServer();

  const serverRenderMiddleware = serverRender(rsbuildServer);

  // SSR rendering when accessing /index.html
  app.get('/', async (req, res, next) => {
    try {
      await serverRenderMiddleware(req, res, next);
    } catch (err) {
      logger.error('SSR render error, downgrade to CSR...\n', err);
      next();
    }
  });

  app.use(rsbuildServer.middlewares);

  const httpServer = app.listen(rsbuildServer.port, async () => {
    await rsbuildServer.afterListen();
  });

  rsbuildServer.connectWebSocket({ server: httpServer });
}

startDevServer(process.cwd());

Modify startup script

After using a custom Server, you need to change the startup command from rsbuild dev to node ./server.mjs.

If you need to preview the online effect of SSR rendering, you also need to modify the preview command. SSR Prod Server example reference: Example.

package.json
{
  "scripts": {
    "build": "rsbuild build",
    "dev": "node ./server.mjs",
    "preview": "node ./prod-server.mjs"
  }
}

Now, you can run the npm run dev command to start the dev server with SSR rendering function, and visit http://localhost:3000/ to see that the SSR content has been rendered to the HTML page.

Examples