服务端渲染(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 渲染占位符:

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

创建 SSR 配置

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

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

rsbuild.config.ts

  1. export default {
  2. environments: {
  3. // 配置 web 环境,用于浏览器端
  4. web: {
  5. source: {
  6. entry: {
  7. index: './src/index.client.js',
  8. },
  9. },
  10. output: {
  11. // 浏览器产物的 target 类型为 'web'
  12. target: 'web',
  13. },
  14. html: {
  15. // 自定义 HTML 模版
  16. template: './index.html',
  17. },
  18. },
  19. // 配置 node 环境,用于 SSR
  20. node: {
  21. source: {
  22. entry: {
  23. index: './src/index.server.js',
  24. },
  25. },
  26. output: {
  27. // Node.js 产物的 target 类型为 'node'
  28. target: 'node',
  29. },
  30. },
  31. },
  32. };

自定义 Server

Rsbuild 并未内置 SSR 渲染能力,你可以通过 Rsbuild 的自定义 ServerEnvironment API 实现 SSR 渲染:

server.mjs

  1. import express from 'express';
  2. import { createRsbuild, loadConfig } from '@rsbuild/core';
  3. // 实现 SSR 渲染功能
  4. const serverRender = (serverAPI) => async (_req, res) => {
  5. // 加载 SSR bundle
  6. const indexModule = await serverAPI.environments.ssr.loadBundle('index');
  7. const markup = indexModule.render();
  8. const template = await serverAPI.environments.web.getTransformedHtml('index');
  9. // 将 SSR 渲染内容插入到 HTML 模版中
  10. const html = template.replace('<!--app-content-->', markup);
  11. res.writeHead(200, {
  12. 'Content-Type': 'text/html',
  13. });
  14. res.end(html);
  15. };
  16. // 自定义 Server
  17. async function startDevServer() {
  18. const { content } = await loadConfig({});
  19. const rsbuild = await createRsbuild({
  20. rsbuildConfig: content,
  21. });
  22. const app = express();
  23. const rsbuildServer = await rsbuild.createDevServer();
  24. const serverRenderMiddleware = serverRender(rsbuildServer);
  25. // 访问 /index.html 时进行 SSR 渲染
  26. app.get('/', async (req, res, next) => {
  27. try {
  28. await serverRenderMiddleware(req, res, next);
  29. } catch (err) {
  30. logger.error('SSR render error, downgrade to CSR...\n', err);
  31. next();
  32. }
  33. });
  34. app.use(rsbuildServer.middlewares);
  35. const httpServer = app.listen(rsbuildServer.port, async () => {
  36. await rsbuildServer.afterListen();
  37. });
  38. rsbuildServer.connectWebSocket({ server: httpServer });
  39. }
  40. startDevServer(process.cwd());

修改启动脚本

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

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

package.json

  1. {
  2. "scripts": {
  3. "build": "rsbuild build",
  4. "dev": "node ./server.mjs",
  5. "preview": "node ./prod-server.mjs"
  6. }
  7. }

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

示例项目