Hydration Failed 的本质是:React 准备给服务器生成的 HTML 绑定事件时,客户端第一次渲染得到的结构或文本不同。修复重点不是隐藏警告,而是找出哪一段输出在服务器和浏览器之间不稳定。
目录
- 最常见原因
- 时间与随机值
- 浏览器 API 与 localStorage
- 无效 HTML 嵌套
- 数据为什么不一致
- 定位步骤
- suppressHydrationWarning 何时使用
- 与 Server/Client Components 的关系
- 参考资料
最常见原因
- 渲染阶段调用
Date.now()、new Date()、Math.random()。 - 用
typeof window !== "undefined"在首次渲染返回不同 JSX。 - 首次渲染直接读取
localStorage、窗口尺寸或媒体查询。 - 服务端和客户端拿到的数据版本不同。
- HTML 标签嵌套无效,浏览器自动修正 DOM。
- 第三方脚本或浏览器扩展在 React 启动前修改页面。
- locale、时区或数字格式在两端不同。
时间与随机值
错误示例:
export function Timestamp() {
return <time>{new Date().toLocaleString()}</time>
}
服务器和客户端的时间、locale、时区都可能不同。稳定方案是由服务器生成一个明确值并作为 props 传入:
export function Timestamp({ iso }: { iso: string }) {
return <time dateTime={iso}>{iso.slice(0, 10)}</time>
}
如果必须显示浏览器本地时间,可以先渲染稳定占位内容,hydration 完成后再更新,同时避免明显布局跳动。
浏览器 API 与 localStorage
不要在渲染阶段根据 window 分支返回不同结构:
// 错误:服务器与客户端首次输出可能不同
return <p>{typeof window === "undefined" ? "server" : "client"}</p>
客户端专属值可以在 Effect 中读取:
"use client"
import { useEffect, useState } from "react"
export function ThemeLabel() {
const [theme, setTheme] = useState("system")
useEffect(() => {
setTheme(localStorage.getItem("theme") ?? "system")
}, [])
return <span>主题:{theme}</span>
}
服务器和客户端第一次都输出 system,随后客户端再同步存储值。若主题必须在首帧就正确,通常需要 cookie、服务端可读偏好或在 React 前执行经过验证的初始化脚本。
无效 HTML 嵌套
浏览器会修复无效结构,例如把块级元素错误放入 <p>。React 接收到的 DOM 已经不是服务器原始字符串:
// 错误
<p>
介绍
<div>详细内容</div>
</p>
改用合法结构并用浏览器 Elements 面板检查实际 DOM。第三方组件、Markdown 转换器和富文本内容尤其需要关注。
数据为什么不一致
- 服务端读取 A 版本,客户端立即请求到 B 版本。
- 服务端使用缓存,客户端绕过缓存。
- 列表排序依赖不稳定比较函数。
- 对象遍历顺序或过滤条件在两端不同。
首屏数据应尽量由 Server Component 获取,再把同一快照传给需要 hydration 的 Client Component。不要让客户端在首次渲染前自行制造另一份默认数据。
定位步骤
- 阅读错误中标出的组件栈和差异文本。
- 缩小到最小可疑 Client Component。
- 搜索
Date、Math.random、window、document、localStorage和 locale 格式化。 - 检查 HTML 嵌套与第三方组件输出。
- 无痕模式关闭扩展后复现。
- 比较页面源码中的服务器 HTML与 hydration 后的 Elements DOM。
- 确认生产构建也能复现,排除开发工具额外行为。
suppressHydrationWarning 何时使用
suppressHydrationWarning 是有限的逃生口,适合你明确知道某个单层文本必然不同,例如不可避免的时间文本。它不会修复数据流,也不应该加在大容器上掩盖未知结构差异。
<time suppressHydrationWarning>{clientSpecificTime}</time>
如果差异涉及子树结构、事件绑定或业务状态,应修复渲染来源。
与 Server/Client Components 的关系
Client Component 可能参与首屏预渲染,随后在浏览器 hydration。把组件标记为 "use client" 并不会自动消除不一致。先理解边界,再决定数据放在哪里,详见 Next.js Server 与 Client Components。
参考资料
继续阅读
Next.js 路由指南:App Router 常见模式
基于当前 Next.js App Router,梳理静态路由、动态路由、catch-all、route groups、parallel routes 和 route handlers 的使用场景,并说明什么时候还会遇到 Pages Router。
10 分钟Next.js Server 与 Client Components 怎么选
解释 App Router 中 Server Component 与 Client Component 的执行位置、数据获取、序列化边界和 hydration,并给出缩小客户端 bundle 的组件拆分方法。
14 分钟Motion for React 完整指南:动画、手势与退出效果
从安装和 motion 组件开始,系统掌握 Motion for React 的 variants、拖拽手势、MotionValue、useTransform 与 AnimatePresence,并了解 Next.js 使用方式和性能边界。
订阅 FreeMac
每周精选:免费 Mac 软件评测、可信来源更新、替代方案和少折腾指南。