Next.js水合失败的常见场景
七崽爱吃小饼干2026/03/02阅读 1
Next.js 水合失败的常见场景
先一句话搞懂:水合是什么?
Next.js 先在服务端生成 HTML → 发给浏览器 浏览器拿到后,用 JS 让页面变成可交互(绑定事件、激活状态) 这个“激活”过程 = 水合(Hydration)
水合失败 = 服务端渲染的 HTML 和客户端渲染的不一致
一、最常见的 8 大水合失败场景
1. 客户端才有的 API 直接在组件顶层使用
直接在组件里写:
js
const isMobile = window.innerWidth < 768
- 服务端没有
window/document - 服务端渲染:undefined
- 客户端渲染:true/false 结果:HTML 对不上 → 水合爆炸
修复:
- 放在
useEffect里 - 或用
dynamic+ssr: false
2. 使用 Date、时间、随机数等服务端/客户端不一致的值
js
// 服务端一个值,客户端一个值
const now = new Date().toISOString()
const random = Math.random()
服务端与客户端生成内容不一样 → 水合失败
修复:
useEffect里获取- 或服务端传时间戳,客户端统一渲染
3. 条件渲染用了浏览器环境变量
js
return (
<div>
{typeof window !== 'undefined' && <ClientComp />}
</div>
)
服务端渲染不显示,客户端显示 → DOM 结构不一致
修复:
用 useEffect 控制显示
4. 使用 localStorage / sessionStorage 直接渲染
js
const theme = localStorage.getItem('theme')
服务端没有 localStorage → 服务端 null,客户端有值 → 不一致
修复:
- useEffect 里读取
- 或使用 cookie + 服务端获取
5. 第三方库(富文本、图表、编辑器、轮播)
像:
- Swiper
- Chart.js
- Editor.md / TinyMCE
- D3.js 这些库依赖 window,服务端渲染不出来,客户端渲染出来 → DOM 不一致
修复:
js
import dynamic from 'next/dynamic'
const Editor = dynamic(() => import('@/components/Editor'), {
ssr: false
})
6. 服务端与客户端数据不一致
例如:
- 服务端获取数据 A
- 客户端请求后变成数据 B 导致列表数量、内容不一样
修复:
确保服务端与客户端使用同一份初始数据
用 getServerSideProps / generateMetadata 统一获取
7. 多余/缺少空格、换行、注释导致不匹配
服务端:<div>hello</div>
客户端:<div> hello </div>
这种也会水合失败(React 18 严格模式)
修复: 保持结构完全一致
8. 使用 useState 初始值依赖浏览器环境
js
const [dark, setDark] = useState(window?.matchMedia('dark'))
服务端无 window → 初始值不一致
修复: useState 初始值设为 null/undefined,useEffect 里再更新
二、水合失败的典型报错
Error: Hydration failed because the initial UI does not match what was rendered on the server.
Text content does not match server-rendered HTML.
There was an error while hydrating. Because the error is non-serializable or was thrown earlier.
三、最快修复水合问题的万能公式
遇到水合报错,按这个顺序排查:
- 是否用了 window / document / navigator 直接渲染?
- 是否用了 new Date() / Math.random()?
- 是否用了 localStorage?
- 是否用了第三方UI库、图表、富文本?
- 是否条件渲染导致服务端客户端结构不同?
万能修复方案:
js
const [isClient, setIsClient] = useState(false)
useEffect(() => {
setIsClient(true)
}, [])
if (!isClient) return null // 或服务端占位
四、给你总结
水合失败 = 服务端渲染的内容 ≠ 客户端渲染内容
最常见原因:
- 直接使用 window / document
- 时间、随机数
- 本地存储 localStorage
- 第三方客户端库
- 条件渲染不一致
解决口诀: 客户端逻辑,全部放进 useEffect! 第三方组件,全部用 dynamic + ssr: false!