Nextjs 使用 dayjs 时出现 Hydration 错误的解决记录
in 码农笔记JavaScript笔记 with 0 comment

Nextjs 使用 dayjs 时出现 Hydration 错误的解决记录

in 码农笔记JavaScript笔记 with 0 comment

在写星火艺坊月历组件的时候,为了方便进行日期的转化和处理,引入了dayjs工具,会出现如下报错:

image-20250801104631798

这个错误意味着服务端渲染(SSR)输出的 HTML 与客户端 React 第一次渲染生成的 DOM 不一致,React 出于安全考虑会丢弃 SSR 内容,完全在客户端重新渲染,导致页面闪烁或布局突变。

问题复现

以一个日历组件为例,该组件在初次渲染时会读取 dayjs()、屏幕尺寸、鼠标位置等“动态值”:

// 服务端渲染阶段:dayjs() => 基于服务器时区的当前日期
// 客户端渲染阶段:dayjs() => 浏览器时区的当前日期
const [currentDate, setCurrentDate] = useState(dayjs());

因此,服务端渲染的 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;

效果验证

最佳实践

通过上述方法,我们便可以彻底消除服务端渲染与客户端首屏渲染的不一致问题,让 React 的 Hydration 过程平滑无痕。如果你也遇到类似的 SSR/CSR 差异报错,不妨参考此方案:先输出“静态占位”→挂载后再填充动态内容。

留言: