Hydration
Hydration 是浏览器复用已有 HTML 并接上客户端组件逻辑、事件与状态的过程;在 RSC 架构中主要发生在需要客户端运行时的边界,而不是所有 Server Component。
[!info] related notes
- 所属 MOC: 前端基础 MOC, 前端工程化 MOC
- 相关概念: 服务端渲染(SSR), 静态站点生成(SSG), CSR、SSR 与 SSG, react-hydration-mismatch, React 服务端组件与客户端组件, React Server Components
- 框架实践: Next.js, Islands Architecture
Hydration
一句话定义
Hydration 是前端框架在浏览器中复用已经存在的 HTML,并把客户端组件的逻辑、事件、状态和后续更新能力接上去的过程。
它解决的是:
页面已经“看得见”以后,如何让需要交互的部分真正“活起来”。
为什么它会出现
SSR、SSG 或其他预渲染方案可以提前给浏览器 HTML:
Server / build
↓
HTML
↓
Browser 立即显示
但 HTML 本身并不知道 React 的:
onClickuseStateuseEffect- 组件更新关系
- 客户端路由状态
因此浏览器还需要客户端 JavaScript 把这些能力恢复起来:
已有 HTML
+
Client Component JS
↓
Hydration
↓
可交互 UI
Hydration 不是“重新生成一遍页面”
一个常见误解是:服务器生成 HTML 后,React 在浏览器里再完整重画一次。
更准确的理解是:客户端会基于已经存在的 DOM/HTML 建立 React 对应关系,并让后续状态更新与事件系统接管它。
因此 hydration 的核心是 reuse + attach,而不是简单的“第二次渲染”。
首次可见不等于首次可交互
用户可能已经看到:
<button>保存</button>
但相关客户端 JavaScript 尚未加载/执行完成时,这块 UI 的 React 交互能力可能还没有完全就绪。
所以要区分:
- HTML 已经可见
- JavaScript 已经可执行
- 交互区域已经 hydration 完成
详见 首次可见与可交互时间。
在 React Server Components 架构中的变化
传统 SSR 经常需要对较大的 React 应用树做 hydration。
RSC 架构进一步问:
哪些组件实现代码根本不需要进入浏览器?
因此:
- Server Component 实现留在服务端,不需要作为客户端组件被 hydration。
- Client Component 边界及需要浏览器 runtime 的部分才需要客户端 JavaScript。
这也是为什么:
Server Component ≠ SSR
Client Component ≠ CSR
一个 Client Component 的初始 UI可以被服务器预渲染,但它仍需要在浏览器中 hydration 才能拥有状态与事件。
与 Islands Architecture 的关系
Islands Architecture 把 hydration 边界进一步显式化:页面大部分保持 HTML,只有标记为交互岛的区域独立 hydration。
Astro 的 client:load、client:idle、client:visible 等指令甚至可以控制岛屿何时加载和 hydration。
它和 RSC 都试图减少不必要的客户端 JavaScript,但实现模型不同。
Hydration Mismatch
如果客户端在 hydration 时“认为应该存在的 UI”和服务器已经输出的 HTML 不一致,就可能出现 hydration mismatch。
常见原因包括:
- 首屏直接使用
Date.now()/ 随机值 - 服务端和客户端拿到不同数据
- 渲染阶段直接访问只存在于浏览器的状态
- 首屏主题、locale 等环境信息两侧不一致
ThemeSwitcher 是一个很典型的边界案例
ThemeSwitcher 本身通常需要:
onClick
localStorage
matchMedia
DOM class / attribute
所以它是 Client Component 很自然。
但“首屏应该是什么主题”是另一个问题。
方案一:主题存在 Cookie / 请求上下文
如果服务端在请求时就能知道主题,例如 Cookie:
Request(cookie=dark)
↓
Server render <html class="dark">
↓
Browser
这样可以从第一帧就输出正确主题。
方案二:主题只存在 localStorage
服务端读取不到浏览器 localStorage。这时不能说“请求时自动把 localStorage 带给服务器”。常见做法是:
- 使用尽早执行的小脚本,在页面绘制前读取主题并设置
<html>class/attribute;或 - 首次客户端运行后再同步主题;或
- 改为 Cookie,使服务端也能读取主题偏好。
所以:
ThemeSwitcher 是 Client Component,不等于主题的首屏决策一定只能在客户端做;关键取决于主题状态存在哪里。
为什么 Hydration 会成为性能问题
Hydration 不是免费的。客户端仍可能需要:
- 下载 JavaScript
- parse / compile
- 执行模块
- 建立组件树
- 绑定事件与状态
- 占用主线程
所以 SSR 能改善“更早看到内容”,并不自动保证“更早能交互”。
优化方向不是“永远不要 hydration”,而是:
减少需要 hydration 的范围
+
减少 Client JS
+
推迟低优先级交互
+
避免 hydration mismatch
常见误区
CSR 默认也需要 Hydration
不准确。纯 CSR 没有一份由服务器预先生成、等待客户端接管的同一套 UI HTML,因此通常讨论的是客户端首次 render,而不是 hydration。
SSR 完成后页面就完全可交互
不准确。SSR 主要解决 HTML 直出;交互能力仍依赖客户端 runtime。
整个 RSC 页面都要 Hydration
不准确。Server Component 本身不需要作为客户端组件 hydration;重点是 Client Component 边界。
最短记忆方式
SSR / SSG 先把页面画出来,Hydration 把需要客户端交互的部分接活。
学习检查
不看正文回答:
- Hydration 的输入为什么是一份已经存在的 HTML,而不是空页面?
- SSR / SSG 与 Hydration 分别解决“可见”和“可交互”的哪一部分?
- 为什么纯 CSR 通常讨论首次 render,而不是 hydration?
- Hydration mismatch 本质上要求哪两侧结果保持什么一致性?
- React Server Component 为什么不等于“所有页面都需要 hydration”?
- 减少 Hydration 成本可以从哪些方向入手?
- 给一个依赖 localStorage 的 ThemeSwitcher,解释为什么容易产生首屏 / hydration 边界问题。