在写星火艺坊月历组件的时候,为了方便进行日期的转化和处理,引入了dayjs工具,会出现如下报错:
这个错误意味着服务端渲染(SSR)输出的 HTML 与客户端 React 第一次渲染生成的 DOM 不一致,React 出于安全考虑会丢弃 SSR 内容,完全在客户端重新渲染,导致页面闪烁或布局突变。
问题复现
以一个日历组件为例,该组件在初次渲染时会读取 dayjs()、屏幕尺寸、鼠标位置等“动态值”:
// 服务端渲染阶段:dayjs() => 基于服务器时区的当前日期
// 客户端渲染阶段:dayjs() => 浏览器时区的当前日期
const [currentDate, setCurrentDate] = useState(dayjs());
- SSR 时 dayjs() 可能是 UTC+0,客户端可能是 UTC+8;
- useIsPC()【这个是我写的hooks,根据视口宽度判断是否为pc】、window.innerWidth、Math.random() 等在两端会产生不同结果。
因此,服务端渲染的 HTML 和客户端挂载时第一次渲染的 HTML 必然会有差异,React 检测到不一致就抛出 Hydration failed。
根本原因
Hydration Mismatch 主要由以下几类原因导致:
1.运行时分支判断
const isClient = typeof window !== 'undefined';
2.时间、随机数等动态数据
Date.now(), Math.random()
3.用户环境差异(时区、locale、屏幕大小)
4.异步/外部数据未同步传输
5.组件标签不合法
核心思路:在 SSR 和客户端首次渲染时都输出完全相同的静态内容,再在客户端挂载后再填充动态内容。
解决方案概览
1.延迟渲染:让组件在服务端和客户端初次渲染阶段输出固定占位或空白,避免任何动态差异。
2.挂载后更新:在 useEffect 中读取真实的动态数据并触发二次渲染。
3.判定挂载:通过一个 isMounted 布尔值,只在客户端挂载后才渲染真正的内容。
具体如何解决呢?
1.初始固定值
把所有依赖动态环境(日期、时区、屏幕大小等)的 useState 初始化,改为一个“常量”占位值(如 "1970-01-01")。
2.挂载标志 isMounted
引入 isMounted,默认为 false,仅在客户端挂载后通过 useEffect 置为 true。
3.空白占位
在 isMounted === false 时提前 return null,此时服务器和客户端都会渲染相同的“空白”输出,保证首屏一致。
4.客户端真实渲染
useEffect 中更新日期、选中状态和 isMounted,触发第二次渲染,页面才真正展示完整日历。
代码示例如下:
const [isMounted, setIsMounted] = useState(false);
// 初始时用固定值,保证 SSR 与首屏一致
const [currentDate, setCurrentDate] = useState(() => dayjs("1970-01-01"));
// 挂载后再更新为真实数据
useEffect(() => {
setCurrentDate(dayjs());
setSelectedDate(parseDate(dayjs().format("YYYY-MM-DD")));
setIsMounted(true);
}, []);
.
// 未挂载时返回 null,避免任何动态差异
if (!isMounted) return null;
效果验证
- 开发环境:关闭浏览器控制台,刷新页面,不再出现 “Hydration failed” 警告。
- 生产环境:SSR 输出与客户端首屏匹配,首次渲染无闪屏,SEO 和首屏加载速度更优。
最佳实践
- 尽量减少 SSR 动态分支:在同构应用中,所有涉及 window、document、Math.random()、Date.now() 的逻辑,都应当在 useEffect 或其他客户端生命周期中执行。
- 使用 next/dynamic 异步加载:对于完全不需要 SSR 的组件,可以使用 next/dynamic 并设 ssr: false。
- SSR 友好的占位:对时区、用户语言、主题色等依赖环境的功能,提供一个固定的占位或闪烁效果。
通过上述方法,我们便可以彻底消除服务端渲染与客户端首屏渲染的不一致问题,让 React 的 Hydration 过程平滑无痕。如果你也遇到类似的 SSR/CSR 差异报错,不妨参考此方案:先输出“静态占位”→挂载后再填充动态内容。
本文由 小但 创作
全文共:2410个字
采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载,均为作者原创,转载前请务必署名
最后编辑时间为: Aug 1, 2025 at 11:17 am