close
logologo
指南
配置
插件
API
社区
版本
更新日志
Rsbuild 0.x 文档
English
简体中文
指南
配置
插件
API
社区
更新日志
Rsbuild 0.x 文档
English
简体中文
logologo

开始

介绍
快速上手
功能导航
名词解释

框架

React
Vue
Preact
Svelte
Solid

基础

命令行工具
开发服务器
构建产物
静态资源
HTML
JSON
Wasm
TypeScript
Web Workers
部署静态站点
升级 Rsbuild

配置

配置 Rspack
配置 Rsbuild
配置 SWC

样式

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

进阶

路径别名
环境变量
模块热更新
浏览器范围
浏览器兼容性
模块联邦
多环境构建
服务端渲染(SSR)
测试

优化

代码拆分
产物体积优化
提升构建性能
静态资源内联

迁移

从 Rsbuild 0.x 迁移
webpack
Create React App
Vue CLI
Vite
Vite 插件
Modern.js Builder

调试

开启调试模式
构建性能分析
使用 Rsdoctor

常见问题

通用类问题
功能类问题
异常类问题
热更新问题
📝 在 GitHub 上编辑此页
上一页多环境构建
下一页测试

#服务端渲染(SSR)

本章节介绍如何使用 Rsbuild 实现 SSR 功能。

值得注意的是,Rsbuild 自身不提供开箱即用的 SSR 能力,而是提供 low-level 的 API 和配置来允许框架开发者实现 SSR。如果你需要使用开箱即用的 SSR 支持,可以考虑使用基于 Rsbuild 的框架,例如 Modern.js。

#什么是 SSR

SSR 是 "Server-side rendering"(服务端渲染)的缩写。它表示由服务器生成网页的 HTML,并将其发送给客户端,而不是只发送一个空的 HTML 外壳,并依赖 JavaScript 来生成页面内容。

在传统的客户端渲染中,服务器会向客户端发送一个空的 HTML 外壳和一些 JavaScript 脚本,然后从服务器的 API 中获取数据,并用动态内容填充页面。这会导致页面的初始加载时间较慢,不利于用户体验和 SEO。

使用 SSR 后,服务器会生成已经包含动态内容的 HTML,并将其发送给客户端。这使得首屏加载速度更快,并对 SEO 更加友好,因为搜索引擎可以爬取到渲染后的页面。

#文件结构

一个典型的 SSR 应用会包含以下文件:

- index.html
- server.js          # 自定义服务器脚本
- src/
  - App.js           # 导出 App 代码
  - index.client.js  # 客户端入口,挂载 App 组件到 Dom 元素
  - index.server.js  # 服务端入口,通过 SSR API 渲染 App 组件

index.html 中需要定义 SSR 占位符:

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

#创建 SSR 配置

SSR 场景下,需要同时产出 web 和 node 两种类型的产物,分别用于客户端渲染(CSR)和服务器端渲染(SSR)。

此时可以使用 Rsbuild 的多环境构建能力,定义如下配置:

rsbuild.config.ts
export default {
  environments: {
    // 配置 web 环境,用于浏览器端
    web: {
      source: {
        entry: {
          index: './src/index.client.js',
        },
      },
      output: {
        // 浏览器产物的 target 类型为 'web'
        target: 'web',
      },
      html: {
        // 自定义 HTML 模版
        template: './index.html',
      },
    },
    // 配置 node 环境,用于 SSR
    node: {
      source: {
        entry: {
          index: './src/index.server.js',
        },
      },
      output: {
        // Node.js 产物的 target 类型为 'node'
        target: 'node',
      },
    },
  },
};

#自定义 Server

Rsbuild 提供了 Dev server API 和 Environment API 来实现 SSR。

以下是一个基础示例:

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;

  // 访问 /index.html 时进行 SSR
  app.get('/', async (req, res, next) => {
    try {
      // 加载服务端产物
      const bundle = await environments.node.loadBundle('index');
      const template = await environments.web.getTransformedHtml('index');
      const rendered = bundle.render();
      // 将渲染内容插入到 HTML 模版中
      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();

#修改启动脚本

使用自定义 Server 后,需要将启动命令由 rsbuild dev 改为 node ./server.mjs。

如果需要预览 SSR 的线上效果,同样需要修改预览命令。SSR Prod Server 示例参考:Example。

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

现在,执行 npm run dev 命令即可启动带有 SSR 功能的开发服务器,访问 http://localhost:3000/ 即可看到 SSR 内容已经渲染到了 HTML 页面上。

#获取资源清单

默认情况下,和当前页面关联的 scripts 和 links 会自动插入到 HTML 模版中,此时通过 getTransformedHtml 即可获取编译后的 HTML 模版内容。

在服务器端动态生成 HTML 时,你需要将 JavaScript 和 CSS 资源的 URL 注入到 HTML 中。通过配置 output.manifest,你可以方便地获取这些资源的清单信息。示例如下:

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((file) => `<script src="${file}" 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>`;
}

#示例项目

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

#在插件中适配 SSR

在开发 Rsbuild 插件时,如果需要针对 SSR 添加特定的逻辑,可以根据 target 进行区分。

  • 通过 modifyEnvironmentConfig 修改 SSR 场景下的 Rsbuild 配置:
export const myPlugin = () => ({
  name: 'my-plugin',
  setup(api) {
    api.modifyEnvironmentConfig((config) => {
      if (config.target === 'node') {
        // SSR 特有的 Rsbuild 配置
      }
    });
  },
});
  • 通过 modifyRspackConfig 修改 SSR 场景下的 Rspack 配置:
export const myPlugin = () => ({
  name: 'my-plugin',
  setup(api) {
    api.modifyRspackConfig((config, { target }) => {
      if (target === 'node') {
        // SSR 特有的 Rspack 配置
      }
    });
  },
});
  • 通过 transform 为 SSR 和客户端分别进行代码转换:
export const myPlugin = () => ({
  name: 'my-plugin',
  setup(api) {
    api.transform({ test: /foo\.js$/, targets: ['web'] }, ({ code }) => {
      // 转换 client 代码
    });

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