先记住这个答案
hydration mismatch 表示客户端首次渲染预期与浏览器中已有的服务端标记不一致。常见来源包括两端读取不同数据快照、渲染时生成时间或随机数、按 window 或设备状态分支、时区和本地化差异、无效 HTML 被浏览器修正,以及扩展或第三方脚本提前修改 DOM。定位时先保留原始响应、实际 DOM 和客户端初始输入,再缩小到首个不同节点;修复应恢复确定性输入或合法结构,而不是整片关闭警告。
- 比较的是客户端第一次输出
- 先核对数据再核对环境和结构
- 浏览器解析后的 DOM 可能不同于原始字符串
先排查两端是否真的使用同一份数据
服务端取到商品库存十件,客户端启动时重新读取到九件,如果直接用新结果进行首次渲染,就可能与已有 HTML 冲突。应明确初始快照传递与后续重新验证时机,让首次接管使用对应的数据。
时间显示也有类似问题。服务端和浏览器各自取当前时间,即使格式相同也可能跨秒;指定相同时区只能解决部分格式差异,不能让两次不同的时间读取自动成为同一个输入。
type PublishedTimeProps = {
iso: string;
initialLabel: string;
};
export default function PublishedTime({ iso, initialLabel }: PublishedTimeProps) {
return <time dateTime={iso}>{initialLabel}</time>;
}服务端生成一次 iso 与 initialLabel,并通过框架的数据机制把相同值交给客户端首次渲染。组件不在渲染中重新读取当前时间或猜测浏览器语言;需要本地化时,可以在接管后按明确策略更新。
再检查环境分支和浏览器解析结果
在渲染中根据 typeof window 是否存在返回两套结构,会让服务端与浏览器天然走不同分支。读取 matchMedia、localStorage 或默认语言也可能改变首次输出,应采用一致的初始值再更新,或划定合理客户端边界。
无效嵌套会被浏览器解析器修正,例如某些块元素放进段落后,实际 DOM 不再等于服务端字符串暗示的结构。检查时要同时看原始响应和元素树,单独比较 JSX 源码可能找不到解析阶段的改写。
最后隔离外部修改并缩小复现
浏览器扩展、翻译工具、第三方脚本或 CDN 转换都可能在接管前改动节点。可以用干净上下文与固定数据复现,再逐步启用相关因素;不能因为自己组件代码看起来一致,就认定警告一定是 React 误报。
调试应记录差异节点附近的组件路径、初始数据版本和发生环境,并注意去除用户敏感信息。先修首个结构或输入差异,再重试观察其他诊断,避免一次给整棵树加抑制属性而丢失根因。
容易答错的地方
- 固定时区就认为所有时间差异都解决了
- 时区只影响时间解释和格式,两端分别读取当前时刻仍然可能不同。应共享时间值和初始标签,或明确接管后的更新时间;本地化库及运行环境差异也需要在实际部署环境中核对。
- 直接关闭 SSR 作为默认修复
- 关闭某个组件的服务端输出可能适合确实只依赖浏览器的功能,但也会改变首屏与可发现内容。应先确认差异来源,优先修复共享数据和结构,不能为隐藏错误无差别放弃整页服务端正文。
面试官还会怎么问?
为什么刷新时出错,站内跳转时不出错?
首次导航可能接管服务端 HTML,站内跳转则可能走客户端更新而没有同样的接管过程。应比较两条路径的数据来源与初始结构,不要把客户端跳转正常当成服务端输出已正确的证据。
CSS 差异也会触发 hydration 错误吗?
单纯样式计算结果不同不一定构成 React 标记差异,但服务端和客户端生成不同 class 或属性时可能出现诊断。应区分样式表加载问题与实际 DOM 属性不一致,再核对样式库的服务端集成。
可以先显示一致的初始提示,再读取浏览器状态吗?
可以,这属于两阶段展示策略,但需要接受额外更新与可能的视觉切换。应保证初始提示对用户有意义、布局稳定,并避免把公开页面关键正文全部推迟到客户端,尤其要验证弱网体验。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。