← 返回题目列表

React hydration mismatch 通常由什么原因导致?如何排查?

困难 第 24 / 27 题 更新于 2026/07/29
ReactSSRHydrationNext.js

简化版

hydration mismatch 是服务端生成的 HTML 和客户端首次渲染结果不一致导致的。常见原因包括随机数、时间、浏览器专属 API、用户态差异、非法 HTML 结构、服务端和客户端数据不一致。排查时先保证首屏初始数据和渲染分支一致。

详细版

SSR 会先输出 HTML,客户端再 hydrate,给已有 DOM 绑定事件并接管页面。 如果客户端首次渲染的结构或文本和服务端 HTML 不一样,就可能出现 mismatch。

// 不推荐直接在渲染中使用
return <div>{new Date().toLocaleString()}</div>

服务端时间和客户端时间可能不同,文本就不一致。解决思路是把不稳定内容延后到 useEffect,或把初始值从服务端明确传给客户端。面试要强调:hydration 阶段要求“客户端第一次渲染要尽量复刻服务端结果”。

完整版教学

一、hydration 是客户端接管服务端 HTML

SSR 的流程不是客户端重新画一遍空页面。 服务端先生成 HTML,浏览器快速展示;随后 React 在客户端运行,复用已有 DOM 并绑定事件。 这个接管过程叫 hydration。

Server render -> HTML
Browser display -> 用户看到内容
Client React render -> 对齐已有 DOM -> 绑定事件

如果客户端第一次 render 的结果和 HTML 对不上,React 就不知道该相信哪边。 轻则警告,重则丢弃部分 DOM 重新渲染,造成闪烁和性能损耗。

二、随机数和时间是最常见诱因

服务端和客户端运行时间不同,随机种子也不同。 在 render 中直接调用 Date.now()Math.random()new Date() 很容易生成不同文本。 这类问题在本地刷新几次可能才出现。

function Bad() {
  return <span>{Math.random()}</span>
}
不稳定来源为什么不一致处理方式
Math.random()两端随机值不同服务端生成后传入
Date.now()两端时间不同固定初始值或客户端 effect
window.innerWidth服务端没有 window客户端挂载后再读
本地存储服务端读不到初始占位,客户端修正

如果一个时间每秒变化,服务端 10:00:00 输出,客户端 10:00:01 hydrate,就可能不一致。 这不是时间 API 错,是渲染阶段用了不稳定输入。

三、浏览器专属 API 不能参与服务端首渲染分支

服务端没有 windowdocumentlocalStoragematchMedia。 如果渲染分支依赖这些 API,服务端和客户端会走不同路径。 例如服务端默认浅色主题,客户端读 localStorage 得到深色主题,首屏 class 就不同。

const theme = localStorage.getItem('theme') // 服务端会出问题
return <div className={theme}>...</div>

更安全的方式是:服务端能知道的通过 cookie 或请求头传入;服务端不知道的先渲染稳定占位,客户端 effect 后再更新。 这样 hydration 首次对齐,之后再做客户端增强。

四、数据不一致会造成结构级 mismatch

SSR 页面通常需要数据。 如果服务端拿到 10 条列表,客户端首次渲染拿到 0 条或 12 条,DOM 结构就对不上。 这在缓存、权限、A/B 实验和接口竞态中很常见。

服务端 HTML:
ul -> 10 个 li

客户端首次 render:
ul -> 0 个 li

结果:
结构不一致

解决思路是把服务端数据序列化到页面,客户端用同一份初始数据启动。 之后再根据缓存策略重新请求。 Next.js、Remix 等框架都有对应的数据注水机制。

五、非法 HTML 结构也会让浏览器偷偷改 DOM

有时 React 输出看似一致,但浏览器解析 HTML 时会自动纠正非法嵌套。 例如 p 标签里放 div,浏览器可能重排 DOM 结构。 客户端 React 按 JSX 预期 hydrate,就会发现实际 DOM 不一样。

// 不合法结构示例
<p>
  文本
  <div>块级元素</div>
</p>

浏览器修正后的 DOM 可能已经不是你 JSX 表达的树。 排查 hydration mismatch 时,不要只盯数据,也要检查 HTML 合法性。

六、排查顺序要从首屏确定性开始

先看警告指出的组件和文本。 再检查该组件 render 中是否使用随机、时间、浏览器 API、环境判断、异步数据。 最后检查 HTML 嵌套和第三方组件是否 SSR 安全。

排查路径:
定位警告组件
检查不稳定值
检查浏览器 API
检查初始数据
检查 HTML 结构
检查第三方库 SSR 支持

记忆钩子:hydration 要求客户端第一眼认出服务端画的那张脸。

如果客户端第一次就换脸,React 接管时自然会迷糊。 所以核心原则是首屏首次渲染保持确定性。

七、常见误区与追问

  • 误区:hydration mismatch 只是样式小问题。 它可能导致 React 放弃复用部分 DOM,带来闪烁、性能损耗和事件异常。
  • 误区:在 render 里用 Math.random 没关系。 服务端和客户端随机值不同,容易导致文本或属性不一致。
  • 误区:suppressHydrationWarning 可以随便用。 它只能压制特定警告,不应掩盖结构性不一致。
  • 追问:localStorage 主题如何避免 mismatch? 可用 cookie 让服务端知道初始主题,或先渲染稳定占位再客户端更新。
  • 追问:为什么非法 HTML 也会导致 mismatch? 浏览器解析时会自动修正 DOM,实际结构和 React 预期不同。
  • 追问:数据请求如何保证一致? 服务端获取的数据要注入客户端作为初始数据,客户端首次渲染复用它。

八、加强记忆

hydration mismatch 记住“首屏确定性”。服务端 HTML 和客户端第一次 render 要对齐;随机数、时间、浏览器 API、数据差异和非法 HTML 都会破坏对齐。修复时优先让初始数据一致,不稳定内容放到客户端 effect 后处理。