close
logologo
Guide
Config
Plugin
API
Community
Version
Changelog
Rsbuild 0.x Doc
English
简体中文
Guide
Config
Plugin
API
Community
Changelog
Rsbuild 0.x Doc
English
简体中文
logologo

Getting Started

Introduction
Quick start
Features
Glossary

Framework

React
Vue
Preact
Svelte
Solid

Basic

CLI
Dev server
Output files
Static assets
HTML
JSON
Wasm
TypeScript
Web Workers
Deploy static site
Upgrade Rsbuild

Configuration

Configure Rspack
Configure Rsbuild
Configure SWC

Styling

CSS
CSS Modules
CSS-in-JS
Tailwind CSS v4
Tailwind CSS v3
UnoCSS

Advanced

Path aliases
Environment variables
Hot module replacement
Browserslist
Browser compatibility
Module Federation
Multi-environment builds
Server-side rendering (SSR)
Testing

Optimization

Code splitting
Bundle size optimization
Improve build performance
Inline static assets

Migration

Migrating from Rsbuild 0.x
webpack
Create React App
Vue CLI
Vite
Vite plugin
Modern.js Builder

Debug

Debug mode
Build profiling
Use Rsdoctor

FAQ

General FAQ
Features FAQ
Exceptions FAQ
HMR FAQ
📝 Edit this page on GitHub
Previous PageMulti-environment builds
Next PageTesting

#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, then fetches 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 content 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 simultaneously, for client-side rendering (CSR) and server-side rendering (SSR) respectively.

You can then use Rsbuild's multi-environment builds capability to 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 provides the Dev server API and environment API to allow you to implement SSR.

Here is a basic example:

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

async function initRsbuild() {
  const rsbuild = await createRsbuild({
    rsbuildConfig: {
      server: {
        middlewareMode: true,
      },
    },
  });
  return rsbuild.createDevServer();
}

async function startDevServer() {
  const app = express();
  const rsbuild = await initRsbuild();
  const { environments } = rsbuild;

  // SSR when accessing /index.html
  app.get('/', async (req, res, next) => {
    try {
      // Load server bundle
      const bundle = await environments.node.loadBundle('index');
      const template = await environments.web.getTransformedHtml('index');
      const rendered = bundle.render();
      // Insert rendered content into HTML template
      const html = template.replace('<!--app-content-->', rendered);

      res.writeHead(200, { 'Content-Type': 'text/html' });
      res.end(html);
    } catch (err) {
      logger.error('SSR failed.');
      logger.error(err);
      next();
    }
  });
  app.use(rsbuild.middlewares);

  const server = app.listen(rsbuild.port, async () => {
    await rsbuild.afterListen();
  });
  rsbuild.connectWebSocket({ server });
}

startDevServer();

#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, 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 function, and visit http://localhost:3000/ to see that the SSR content has been rendered to the HTML page.

#Get manifest

By default, scripts and links associated with the current page are automatically inserted into the HTML template. At this time, the compiled HTML template content can be obtained through getTransformedHtml.

When you need to dynamically generate HTML on the server side, you will need to inject the URLs of JavaScript and CSS assets into the HTML. By configuring output.manifest, you can easily obtain the manifest information of these assets. Here is an example:

rsbuild.config.ts
export default {
  output: {
    manifest: true,
  },
};
server.ts
async function renderHtmlPage(): Promise<string> {
  const manifest = await fs.promises.readFile('./dist/manifest.json', 'utf-8');
  const { entries } = JSON.parse(manifest);

  const { js, css } = entries['index'].initial;

  const scriptTags = js
    .map((url) => `<script src="${url}" defer></script>`)
    .join('\n');
  const styleTags = css
    .map((file) => `<link rel="stylesheet" href="${file}">`)
    .join('\n');

  return `
    <!DOCTYPE html>
    <html>
      <head>
        ${scriptTags}
        ${styleTags}
      </head>
      <body>
        <div id="root"></div>
      </body>
    </html>`;
}

#Examples

  • SSR + Express Example
  • SSR + Express + Manifest Example

#SSR-specific plugins

When developing Rsbuild plugins, if you need to add specific logic for SSR, you can distinguish it by target.

  • Modify Rsbuild configuration for SSR via modifyEnvironmentConfig:
export const myPlugin = () => ({
  name: 'my-plugin',
  setup(api) {
    api.modifyEnvironmentConfig((config) => {
      if (config.target === 'node') {
        // SSR-specific Rsbuild config
      }
    });
  },
});
  • Modify Rspack configuration for SSR via modifyRspackConfig:
export const myPlugin = () => ({
  name: 'my-plugin',
  setup(api) {
    api.modifyRspackConfig((config, { target }) => {
      if (target === 'node') {
        // SSR-specific Rspack config
      }
    });
  },
});
  • Transform code for SSR and client separately via transform:
export const myPlugin = () => ({
  name: 'my-plugin',
  setup(api) {
    api.transform({ test: /foo\.js$/, targets: ['web'] }, ({ code }) => {
      // transform client code
    });

    api.transform({ test: /foo\.js$/, targets: ['node'] }, ({ code }) => {
      // transform server code
    });
  },
});