症状
控制台报 “Hydration failed because the server rendered HTML didn’t match the client”;页面加载后立即闪烁或重新渲染;时间戳、随机值或主题在 view-source 与渲染后的 DOM 中不同。
诊断
用 curl -s https://example.com/page | grep -A2 -B2 'timestamp\|theme' 查看服务端输出。搜索组件中的非确定性来源:Math.random、Date.now()、new Date()、window、localStorage 在 render 期间执行都会导致两端不同。检查 hydration 错误附近的 HTML,常见问题是非法嵌套(如 div 在 p 内、button 在 button 内),浏览器会静默修复,从而改变 DOM。
根因
React 在 hydration 时复用服务端渲染的 DOM。如果首次客户端渲染产生不同标记,React 无法可靠挂载事件处理器并报告不匹配。差异来自非确定性值、render 阶段读取浏览器 API、非法 HTML 或环境相关的格式化。
修复
把浏览器专属值移到 useEffect 或挂载后初始化;为首次渲染使用稳定默认值;显式指定 locale 与时区;修复非法嵌套。suppressHydrationWarning 只用于有意且局部的差异,不能用来掩盖真正的不匹配。
// ❌ 服务端与客户端不同
function Clock() {
return <span>{Date.now()}</span>;
}
// ✅ 首次渲染稳定,挂载后更新
function Clock() {
const [now, setNow] = useState<number | null>(null);
useEffect(() => setNow(Date.now()), []);
return <span>{now ?? '—'}</span>;
}
验证
禁用缓存硬刷新,确认 hydration 警告消失;比较服务端 HTML 与客户端 DOM;禁用 JavaScript 查看服务端渲染内容;为组件添加相同 props 下服务端与客户端渲染一致的测试。
避免全局抑制
suppressHydrationWarning 适合单个有文档说明的可变文本节点,例如带降级值的时间戳。大范围使用会隐藏真实差异,增加后续调试难度。