ReactNext.jsHydrationSSR

React Hydration Failed:原因、定位与修复

系统排查服务器 HTML 与客户端首次渲染不一致的问题,覆盖时间、随机数、浏览器 API、无效 HTML、客户端存储、第三方扩展和 suppressHydrationWarning 的边界。

·更新于 ·阅读约 10 分钟·计算中...
React Hydration Failed:原因、定位与修复

Hydration Failed 的本质是:React 准备给服务器生成的 HTML 绑定事件时,客户端第一次渲染得到的结构或文本不同。修复重点不是隐藏警告,而是找出哪一段输出在服务器和浏览器之间不稳定。

目录

最常见原因

  1. 渲染阶段调用 Date.now()new Date()Math.random()
  2. typeof window !== "undefined" 在首次渲染返回不同 JSX。
  3. 首次渲染直接读取 localStorage、窗口尺寸或媒体查询。
  4. 服务端和客户端拿到的数据版本不同。
  5. HTML 标签嵌套无效,浏览器自动修正 DOM。
  6. 第三方脚本或浏览器扩展在 React 启动前修改页面。
  7. 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。不要让客户端在首次渲染前自行制造另一份默认数据。

定位步骤

  1. 阅读错误中标出的组件栈和差异文本。
  2. 缩小到最小可疑 Client Component。
  3. 搜索 DateMath.randomwindowdocumentlocalStorage 和 locale 格式化。
  4. 检查 HTML 嵌套与第三方组件输出。
  5. 无痕模式关闭扩展后复现。
  6. 比较页面源码中的服务器 HTML与 hydration 后的 Elements DOM。
  7. 确认生产构建也能复现,排除开发工具额外行为。

suppressHydrationWarning 何时使用

suppressHydrationWarning 是有限的逃生口,适合你明确知道某个单层文本必然不同,例如不可避免的时间文本。它不会修复数据流,也不应该加在大容器上掩盖未知结构差异。

<time suppressHydrationWarning>{clientSpecificTime}</time>

如果差异涉及子树结构、事件绑定或业务状态,应修复渲染来源。

与 Server/Client Components 的关系

Client Component 可能参与首屏预渲染,随后在浏览器 hydration。把组件标记为 "use client" 并不会自动消除不一致。先理解边界,再决定数据放在哪里,详见 Next.js Server 与 Client Components

参考资料

订阅 FreeMac

每周精选:免费 Mac 软件评测、可信来源更新、替代方案和少折腾指南。