~/ ?.log $
返回文章列表
12 min read
更新于 2026年3月6日

我不知道的 React(15)— RSC vs SSR:服务端渲染范式详解

很多人以为 React Server Components(RSC)就是 SSR 的升级版——"以前在服务端渲染 HTML,现在换了个更好的方式"。但实际上,RSC 和 SSR 解决的是完全不同的问题,产出的东西也不一样,它们更像是两个可以叠加使用的独立技术层。

很多人以为 React Server Components(RSC)就是 SSR 的升级版——“以前在服务端渲染 HTML,现在换了个更好的方式”。但实际上,RSC 和 SSR 解决的是完全不同的问题,产出的东西也不一样,它们更像是两个可以叠加使用的独立技术层。

这篇把 RSC、SSR、以及经常被一笔带过的”水合”(Hydration)三个概念拆开讲。它们之间的关系搞清楚了,现代 React 的服务端渲染架构就能看明白大半。

一、SSR 做了什么

SSR(Server-Side Rendering)的核心逻辑很简单:在服务器上执行 React 代码,把组件树渲染成 HTML 字符串,发送给浏览器。

浏览器拿到 HTML 后可以直接解析和显示,不需要等 JavaScript 下载和执行完毕。用户看到页面内容的时间(First Contentful Paint)比纯客户端渲染快得多,搜索引擎的爬虫也能直接抓取到页面内容。

但 SSR 有一个容易被忽略的问题——服务器生成的 HTML 是”死的”。 按钮点了没反应,输入框打不了字,所有事件监听器都不存在。要让页面变得可交互,还需要一个”水合”步骤(后面单独讲)。

说白了,SSR 解决的是”让用户更快看到内容”的问题,不是”让页面更快可交互”的问题。这两者之间有一个时间差——用户看到了页面,但点不了按钮——这个时间差就是水合的开销。

二、RSC 做了什么

React Server Components 的设计目标和 SSR 完全不同。它不是为了让用户更快看到页面,而是为了减少发送到客户端的 JavaScript 体积

RSC 是一种只在服务器上运行的组件。它可以直接访问数据库、读取文件系统、调用内部微服务——就像写后端代码一样。它的渲染结果不是 HTML,而是一种叫 RSC Payload 的特殊序列化格式——本质上是一棵序列化的 React 元素树。

这里有一个很多人会忽略的细节——RSC 及其依赖的库不会被打包进客户端的 JavaScript 中。

比如一个 RSC 组件里引用了 marked(一个 Markdown 解析库,体积不小)来渲染文章内容。如果这是一个客户端组件,marked 的代码会被打进 bundle,用户得下载它。但如果是 RSC,marked 只在服务器上执行,客户端收到的只是渲染好的结果——一段 HTML 描述,不含 marked 的任何代码。

换句话说,RSC 把”执行代码”和”看到结果”拆开了——代码在服务器跑,结果传给客户端。而传统客户端组件是”代码和执行都在客户端”。

但 RSC 有一个硬限制:不能使用任何客户端能力。 不能用 useState、不能用 useEffect、不能绑定事件监听器、不能访问 windowdocument。它是纯粹的”渲染层”——把数据变成 UI 描述,仅此而已。需要交互的部分必须用客户端组件('use client')来实现。

三、RSC 和 SSR 的根本区别

维度SSRRSC
解决什么问题加快首屏内容显示(FCP)、改善 SEO减少客户端 JS 体积、简化数据获取
产物是什么HTML 字符串RSC Payload(序列化的元素树)
能用 useState 吗能(初始渲染时用,交互靠水合)不能
能绑事件吗能(水合后生效)不能
客户端 JS 体积影响组件代码仍会打包到客户端组件代码不进客户端 bundle

这两者不是替代关系,而是可以叠加使用的。 在 Next.js App Router 中,默认就是 RSC + SSR 的组合——服务器先执行 RSC 生成 Payload,再将 RSC Payload 和客户端组件的初始 HTML 一起通过 SSR 输出给浏览器。用户快速看到页面内容(SSR 的功劳),同时非交互部分的 JS 没有被发送到客户端(RSC 的功劳)。

四、水合到底在做什么

水合(Hydration)是 SSR 之后必须发生的一步——把服务器渲染的静态 HTML 变成可交互的 React 应用。

具体流程是这样的:

(1) 浏览器收到服务器返回的 HTML,解析并显示。此时用户能看到完整的页面内容,但所有交互都不可用。

(2) 浏览器开始下载页面所需的 JavaScript 包——React 库本身和所有客户端组件的代码。

(3) JavaScript 加载完成后,React 在内存中根据客户端组件代码重新构建一棵虚拟 DOM 树。

(4) React 拿着这棵虚拟 DOM 树去和已有的真实 DOM(服务器渲染的 HTML)做比对。在比对过程中完成两件事:验证服务端和客户端的渲染结果是否一致(不一致会在控制台报 hydration mismatch 警告),以及把事件监听器绑定到对应的 DOM 节点上。

(5) 水合完成,页面变得完全可交互。

问题的关键在于——水合的开销不小。 客户端需要下载和执行 JS、重建 VDOM、遍历整棵 DOM 树做比对。这些操作都跑在浏览器主线程上,页面虽然已经显示了(FCP 很快),但按钮可能还点不了(TTI 延迟)。在复杂页面上,水合可能要几百毫秒甚至更长。

RSC 如何减少水合开销

RSC 渲染的部分不需要传统意义上的水合。因为 RSC 的产物是 Payload(已经序列化好的元素描述),客户端 React 直接解析这个 Payload 更新 DOM,不需要”重建 VDOM → 比对 HTML”这个步骤。

但页面中标记为 'use client' 的客户端组件仍然需要水合——它们的 HTML 是 SSR 生成的,交互能力要靠客户端 JS 来激活。

RSC 的实际效果是减少了需要水合的组件数量。 原来整个页面都需要水合,现在只有客户端组件需要水合,其余部分由 RSC 直接处理。需要水合的范围越小,TTI 就越快。

五、水合的优化方向

水合的性能问题催生了几种不同层次的优化方案。

选择性水合(Selective Hydration),React 18 引入的特性。传统水合是一次性的——整棵树从上到下全部水合完才算结束。选择性水合配合 Suspense 边界,允许 React 分块水合,优先处理用户正在交互的区域。比如用户点了一个按钮,即使其他区域还没水合完,React 会优先水合这个按钮所在的区域。

部分水合(Partial Hydration),框架层面的优化。Astro 的 Islands 架构是典型代表——页面大部分是纯静态 HTML,只有明确标记为”交互岛屿”的组件才会被水合。非交互部分从头到尾都是静态的,根本不走 React 的水合流程。

可恢复性(Resumability),Qwik 框架提出的理念,目标是彻底消除水合。它在服务端序列化所有必要的状态和事件绑定信息,客户端 JavaScript 可以直接”恢复”执行,不需要重建 VDOM、不需要遍历 DOM、不需要重新绑定事件。这是最激进的方案,也是离主流实践最远的。

六、实际项目中怎么选

如果你只记住一句话,记住这个:SSR 解决”看到”的速度,RSC 解决”下载”的体积,水合是把”看到”变成”能用”的桥梁。

纯客户端渲染(CSR)适合不需要 SEO、首屏速度不敏感的应用,比如内部管理后台。开发最简单,但首屏白屏时间长、JS 包大。

SSR 适合内容型网站、电商、SEO 需求高的场景。用户能快速看到页面,搜索引擎能抓取内容。代价是服务器负载增加,且水合带来 TTI 延迟。

RSC + SSR 是目前 Next.js App Router 默认推荐的模式。静态展示型组件用 RSC(零客户端 JS),需要交互的组件用客户端组件(该水合的还是水合),两者结合得到最优的 FCP 和最小的客户端 JS 体积。代价是架构更复杂,需要开发者明确区分哪些组件需要交互、哪些不需要。

问题的关键在于——这不是一个”选 A 还是选 B”的问题,而是”不同部分用不同策略”。同一个页面里,头部导航可以是客户端组件(需要交互),文章正文可以是 RSC(纯展示),侧边栏推荐可以是 RSC + Suspense(需要异步获取数据但不需要交互)。组件级别的渲染策略选择,才是现代 React 服务端架构的核心思维方式。


本系列其他文章:

相关主题:

share.ts

// 觉得这篇文章有帮助?

// 欢迎分享给更多人

export const  subscribe  =  "/rss.xml" ;