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

如何解决React/Remix应用中与日期相关的Hydration错误?

Remix 水合错误:时区差异导致服务端与客户端渲染内容不匹配

我在业余项目中学习React服务端渲染(使用Remix 1.10版本),遇到了棘手的水合错误:代码能运行但存在缺陷,页面加载时元素闪烁,控制台抛出多个错误:

  • Uncaught Error: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.
  • Uncaught Error: Hydration failed because the initial UI does not match what was rendered on the server.
  • Warning: Text content did not match. Server: "1/29/2023, 10:44:09 AM" Client: "1/29/2023, 12:44:09 PM"

问题根源是服务器处于UTC时区,客户端时区不确定(本次测试为GMT+2),直接在客户端用toLocaleString格式化时间会导致服务端渲染的内容和客户端不一致。我不想修改服务器时区,这不是合理方案。

最简复现代码:

// routes/example.tsx
import { useLoaderData } from "@remix-run/react"
import {json, LoaderArgs } from "@remix-run/server-runtime"

export async function loader({ request }: LoaderArgs) {
  const timestampFromDB = "2023-01-29T10:44:09.672Z"
  
  return json({ time: timestampFromDB })
}

export default function HydrationError() {
  const loaderData = useLoaderData<typeof loader>()
  const time = new Date(loaderData.time)
  const stamp = time.toLocaleString("en-US")

  return (
    <div>
      Time: 
      <time>{stamp}</time>
    </div>
  )
}

解决思路

要避免水合错误,必须保证服务端和客户端渲染的内容完全一致,以下是两种可行方案:

方案1:服务端格式化时间并指定UTC时区,客户端直接复用结果

直接在loader中完成时间格式化,强制指定UTC时区,让服务端和客户端渲染的字符串完全一致,从根源消除差异。

修改后的代码:

// routes/example.tsx
import { useLoaderData } from "@remix-run/react"
import {json, LoaderArgs } from "@remix-run/server-runtime"

export async function loader({ request }: LoaderArgs) {
  const timestampFromDB = "2023-01-29T10:44:09.672Z"
  // 服务端格式化时指定UTC时区
  const formattedTime = new Date(timestampFromDB).toLocaleString("en-US", {
    timeZone: "UTC"
  })
  
  return json({ time: formattedTime })
}

export default function HydrationFixed() {
  const loaderData = useLoaderData<typeof loader>()

  return (
    <div>
      Time: 
      <time>{loaderData.time}</time>
    </div>
  )
}

方案2:水合完成后再切换为客户端本地时区时间

如果需要显示用户本地时区的时间,可以先渲染服务端返回的UTC格式化字符串,等水合完成后再更新为本地时区时间,既保证水合成功,又满足本地时间显示需求。

修改后的代码:

// routes/example.tsx
import { useLoaderData, useEffect, useState } from "@remix-run/react"
import {json, LoaderArgs } from "@remix-run/server-runtime"

export async function loader({ request }: LoaderArgs) {
  const timestampFromDB = "2023-01-29T10:44:09.672Z"
  // 服务端返回UTC格式化的时间
  const utcTime = new Date(timestampFromDB).toLocaleString("en-US", {
    timeZone: "UTC"
  })
  
  return json({ utcTime, rawTime: timestampFromDB })
}

export default function HydrationFixed() {
  const loaderData = useLoaderData<typeof loader>()
  const [displayTime, setDisplayTime] = useState(loaderData.utcTime)

  useEffect(() => {
    // 水合完成后,切换为客户端本地时区的时间
    const localTime = new Date(loaderData.rawTime).toLocaleString("en-US")
    setDisplayTime(localTime)
  }, [loaderData.rawTime])

  return (
    <div>
      Time: 
      <time>{displayTime}</time>
    </div>
  )
}

方案说明

  • 方案1适合不需要显示客户端本地时区的场景,实现简单,完全避免水合问题。
  • 方案2适合需要展示用户本地时区时间的场景,先保证水合成功,再在客户端静默更新内容,几乎不会出现闪烁问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 20:50:23