DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

React 日期 hydration 不匹配,先让两端使用同一时间和首帧文本

服务器和浏览器日期不一致,检查秒与毫秒、默认时区及独立时钟,让首帧使用同一文本,再按需要显示本地时间。

页面日期在本地正常,部署后报 Text content does not match server-rendered HTML,服务端显示一个时间,浏览器显示另一个。先对比传入的时间戳和格式化条件:Date 在两个环境都能使用,但它们的默认时区、语言和当前时钟未必相同。

这里针对参与 hydration 的组件,包括 Next.js 预渲染的 Client Component。仅在服务端完成输出、浏览器没有再次渲染其日期文本的组件,需要按实际边界判断,不能把所有服务端日期都归为 hydration 问题。

先分清时间输入和显示方式

保存实际服务端 HTML、首次客户端输出以及传给组件的值。核对 API 给的是 Unix 秒、毫秒,还是带时区的日期字符串;只有输入确为秒时才乘以 1000,不能对已经是毫秒的值再做转换。

同一时间戳在服务器的 getHours() 与浏览器的 getHours() 中,可能按不同本地时区得到不同小时。省略 locale 或 timeZone 的格式化,也可能显示不同日期、顺序或标点。

另一条常见路径是两端在渲染时各自调用 Date.now() 或 new Date()。即使都使用 UTC,服务器生成页面与浏览器接管页面也发生在不同时刻;跨秒、跨分钟时仍可能不一致。统一时区不能替代统一输入。

要表示同一事件时间,就从服务端取得一个确定值,并把它作为页面数据传给浏览器。要显示持续更新的当前时间,则先约定相同的初始内容,挂载后再启动时钟,不让两套时钟独立决定首帧。

首帧保持一致,挂载后再显示浏览器时区

如果产品需要先输出服务端 HTML,再切换到访客本地时间,可以把服务端已经生成的文本一同传给组件。下面是 Next.js Client Component 的结构示例:调用方必须提供有效的毫秒时间戳,以及针对该时间生成的 initialText,两项都随页面数据传递。

'use client';

import { useEffect, useState } from 'react';

export default function DateLabel({ timestampMs, initialText }) {
  const [localValue, setLocalValue] = useState(null);

  useEffect(() => {
    const text = new Intl.DateTimeFormat(undefined, {
      year: 'numeric',
      month: '2-digit',
      day: '2-digit',
      hour: '2-digit',
      minute: '2-digit',
    }).format(new Date(timestampMs));
    setLocalValue({ timestampMs, text });
  }, [timestampMs]);

  const text = localValue?.timestampMs === timestampMs
    ? localValue.text
    : initialText;

  return (
    <time dateTime={new Date(timestampMs).toISOString()}>
      {text}
    </time>
  );
}

例如调用方可以把该事件的 ISO UTC 字符串作为 initialText,而不是让浏览器首次渲染重新选取默认格式。示例的初始状态在服务端与浏览器都是 null,因此首帧都使用同一份传入文本;Effect 才按浏览器环境格式化。

timestampMs 改变会重新运行 Effect。新值还没格式化时,组件先显示对应的新初始文本,避免上一事件的本地时间暂时冒充新事件。非法时间输入应在调用边界按业务规则处理,不能传入错误值再靠隐藏 hydration 警告兜底。

这种两次渲染会产生一次显示变化,慢连接下 UTC 文本可能持续更久。日期长度变化也可能影响布局。是否采用占位、固定空间或始终显示 UTC,应由页面需求决定;隐藏文本还要考虑无 JavaScript 和辅助技术下的实际可读性。

如果首帧就必须显示已知用户时区,服务端需要取得这项设置,并把同样的格式化结果或条件传给浏览器。共享缓存也要按内容差异正确处理,不能将某位访客的本地时间文本作为所有人的首帧。

不用警告开关替代输入修正

suppressHydrationWarning 只作用于一层,是处理不可避免差异的有限出口。它不会修正时间输入,也不能保证 React 会补上不一致的文本。已经能统一的日期,不必先把报错隐藏掉。

typeof window 分支同样可能让首次输出分成两套。浏览器 API 在服务端不能访问时,要另查导入和渲染边界;这里的 Date 本身可以执行,重点是值与显示方式不同。

保存原组件、时间数据和格式设置后,检查完整加载、不同浏览器时区、跨日期边界以及组件数据更新。确认控制台不再报对应 mismatch,首次文本与服务端一致,随后本地显示对应同一事件。再测试 JavaScript 延迟或不可用时页面仍有可理解内容。回退时一起恢复输入传递和组件逻辑,不能只删 Effect 却留下不同的首帧数据。