You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

修复sortDate导致的服务端渲染与客户端HTML内容不匹配报错

Text content does not match server-rendered HTML 报错修复

问题背景

触发报错的业务代码如下:

<span>
  {sortDate(e.createdAt)}
</span>
  • 代码中调用的sortDate函数用于返回「10秒前」格式的相对时间文本
  • 报错根因:服务端渲染时sortDate计算得到的相对时间(例如18秒前),和客户端完成资源加载、执行水合时因时间流逝重新计算的结果(例如19秒前)不一致,触发框架的水合DOM校验失败
  • 约束:sortDate已在10+页面复用,不接受基于useEffect逐组件改造的方案

修复方案

所有方案均只需要修改sortDate工具函数本身,不需要改动业务侧已有调用代码。

方案1:水合阶段对齐时间粒度(改造成本最低)

修改sortDate内部逻辑,水合完成前不输出精确到秒的时间,统一使用不会因几秒传输延迟产生变化的时间粒度,水合完成后再自动切换为精确时间展示:

// 全局水合标记,仅页面首次加载时为false
let appHydrated = false;

export function sortDate(timestamp) {
  const now = Date.now();
  const diff = now - timestamp;

  // 水合阶段使用粗粒度时间,保证两端计算结果一致
  if (!appHydrated) {
    if (diff < 60 * 1000) return '刚刚';
    if (diff < 3600 * 1000) return `${Math.floor(diff / 60000)}分钟前`;
    // 小时、天及以上粒度的时间差,不会因几秒延迟产生变化,无需特殊处理
  }

  // 客户端水合完成后,标记状态并触发一次全局更新展示精确时间
  if (!appHydrated && typeof window !== 'undefined') {
    appHydrated = true;
    queueMicrotask(() => {
      window.dispatchEvent(new Event('app-time-sync'));
    });
  }

  // 原有精确计算逻辑保持不变
  if (diff < 60 * 1000) return `${Math.floor(diff / 1000)}秒前`;
  if (diff < 3600 * 1000) return `${Math.floor(diff / 60000)}分钟前`;
  if (diff < 86400 * 1000) return `${Math.floor(diff / 3600000)}小时前`;
  // 后续天、月、年的计算逻辑保留原有实现即可
}

// 全局绑定时间更新事件,业务侧如果有定时刷新时间的逻辑可以监听这个事件
if (typeof window !== 'undefined') {
  window.addEventListener('app-time-sync', () => {
    // 这里可以对接项目已有的全局状态,触发所有用到时间的组件重渲染
  }, { once: true });
}

方案2:统一两端首次渲染的时间基准(保留秒级精度)

如果业务要求水合阶段也要展示精确到秒的时间,可以在服务端渲染时将渲染时刻的时间戳注入全局,客户端首次水合计算时间差时统一使用该时间戳,水合完成后再切换为本地真实时间:

  1. 服务端渲染入口注入全局时间戳(以Next.js为例,在根layout或自定义Document中添加):
<script
  dangerouslySetInnerHTML={{
    __html: `window.__SSR_RENDER_AT__ = ${Date.now()}`
  }}
/>
  1. 修改sortDate的时间获取逻辑:
let appHydrated = false;

function getCurrentRenderTime() {
  // 服务端直接返回当前时间
  if (typeof window === 'undefined') return Date.now();
  // 水合完成前使用服务端注入的时间,保证两端计算基准一致
  if (!appHydrated) return window.__SSR_RENDER_AT__;
  // 水合完成后返回真实本地时间
  return Date.now();
}

export function sortDate(timestamp) {
  const diff = getCurrentRenderTime() - timestamp;
  // 原有精确计算逻辑完全不需要改动
  if (diff < 60 * 1000) return `${Math.floor(diff / 1000)}秒前`;
  // 其余时间粒度计算逻辑保持原样
}

// 水合完成后标记状态并触发更新
if (typeof window !== 'undefined') {
  window.addEventListener('load', () => {
    appHydrated = true;
    window.dispatchEvent(new Event('app-time-sync'));
  }, { once: true });
}

避坑提示

  • 不要直接给元素加suppressHydrationWarning属性绕过报错,该属性仅能临时掩盖单行文本的水合警告,会导致其他真实DOM不匹配问题无法被及时发现,长期维护隐患极大
  • 所有相对时间计算逻辑必须收口到sortDate工具函数,禁止在业务组件中单独编写时间差计算逻辑,避免出现漏改场景

内容的提问来源于stack exchange,提问作者eldernyPro

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 17:16:17