巨坑!服務端渲染中,時間處理不當帶來的水合錯誤

巨大的坑

寫在前面

水合錯誤(Hydration Error)是 React 在客戶端「水合」服務器渲染 HTML 時,發(fā)現(xiàn)服務器生成的 HTML 與客戶端渲染的結(jié)果不一致,從而拋出的錯誤或警告。

?? 什么是“水合”?

在 SSR(Server Side Rendering)中,React 首先在服務器上生成 HTML,再發(fā)送到瀏覽器,瀏覽器接收到 HTML 后會“水合(hydrate)”——也就是把 React 的事件和狀態(tài)邏輯綁定到已有的 HTML 上,形成一個可交互的 React 應用。

如果 HTML 和客戶端生成的內(nèi)容不一致,就會報錯。

?水合錯誤的常見原因

? 1. 使用了瀏覽器環(huán)境特有對象(如 window, document)

服務端是 Node.js,沒有瀏覽器環(huán)境,這些對象是 undefined:

// ? 錯誤:這會在 SSR 階段就執(zhí)行
const width = window.innerWidth

應該這樣寫:

// ? 正確:只在客戶端執(zhí)行
useEffect(() => {
  const width = window.innerWidth
}, [])
? 2. 首次渲染時,數(shù)據(jù)不一致(如時間戳、隨機數(shù))
// ? 錯誤:每次 SSR 和 CSR 結(jié)果不一樣
const now = Date.now() // 會導致 HTML 內(nèi)容不同

解決方式:

// ? 正確:延后到 useEffect 里執(zhí)行
const [now, setNow] = useState(0)
useEffect(() => {
  setNow(Date.now())
}, [])
? 3. 組件內(nèi)部條件渲染邏輯服務端/客戶端結(jié)果不同

例如依賴 localStorage 或用戶登錄狀態(tài):

// ? localStorage 在 SSR 中不存在
const isLoggedIn = localStorage.getItem('token')

解決方式:

const [isClient, setIsClient] = useState(false)
useEffect(() => setIsClient(true), [])

return isClient ? <LoggedInUI /> : null
? 4. 使用了某些只支持客戶端的第三方庫(如圖表、動畫庫)

很多 UI 庫依賴瀏覽器 API,如果直接 SSR 會失敗??梢杂茫?/p>

const Chart = dynamic(() => import('./Chart'), { ssr: false })
做法 是否推薦 說明
useEffect 中訪問瀏覽器 API ? 推薦 避免在 SSR 階段運行
使用 typeof window !== 'undefined' 判斷環(huán)境 ? 推薦 確保客戶端執(zhí)行
避免初始渲染用隨機數(shù)/時間戳等 ? 推薦 SSR/CSR 結(jié)果應一致
使用 dynamic(..., { ssr: false }) 僅在客戶端加載組件 ? 有用,但注意 SEO 影響

巨坑:時間格式化帶來的水合問題

上面介紹了幾種水合問題和解決方式,但實際應用中,還是會有一些坑,讓人防不勝防
比如:

同樣的時間戳1752328800,為什么會出現(xiàn)在服務端返回的html中是2025-07-12 14:00:00;而客戶端中是2025-07-12 22:00:00

這是一個經(jīng)典的時區(qū)不一致問題 —— 服務端和客戶端對同一個時間戳的解析結(jié)果不一致,導致你看到:

  • 服務端返回 HTML 中時間是:2025-07-12 14:00:00

  • 客戶端渲染時間卻是:2025-07-12 22:00:00

解釋

服務端(Node.js)默認使用 UTC 時區(qū)

在服務器中(比如 Vercel、Next.js 的 getServerSideProps),你寫:

new Date(1752328800 * 1000).toLocaleString()
// → "2025-07-12 14:00:00"(UTC)
客戶端(瀏覽器)使用 用戶本地時區(qū)

如果你人在日本(UTC+9),瀏覽器會默認用你的系統(tǒng)時區(qū):

new Date(1752328800 * 1000).toLocaleString()
// → "2025-07-12 23:00:00"(UTC+9)

你看到的是 22:00:00,可能是因為瀏覽器或系統(tǒng)設成了 UTC+8。

所以直接在JSX中使用dayjs.unix(Number(1752328800)).format('YYYY-MM-DD HH:mm:ss')的方式進行格式化展示,就會造成水合錯誤

解決方案:
export function useLocalTime(timestamp: number, format = 'YYYY-MM-DD HH:mm:ss') {
  const [value, setValue] = useState('')

  useEffect(() => {
    setValue(dayjs.unix(timestamp).format(format))
  }, [timestamp, format])

  return value
}
const formatted = useLocalTime(1752328800)
return <span>{formatted}</span>

也就是說使用useEffect,確保是客戶端才進行訪問。

小坑,直接使用new Date()

在代碼中直接使用new Date(),會導致客戶端取值和服務端取值不一致,導致水合問題。
同樣可以放到useEffect中解決

總結(jié)

?? 一切包含“瀏覽器環(huán)境依賴(如 Date、window、時區(qū))”的代碼,都應該放在 useEffect 中或客戶端-only組件中執(zhí)行

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • """1.個性化消息: 將用戶的姓名存到一個變量中,并向該用戶顯示一條消息。顯示的消息應非常簡單,如“Hello ...
    她即我命閱讀 5,852評論 0 6
  • 為了讓我有一個更快速、更精彩、更輝煌的成長,我將開始這段刻骨銘心的自我蛻變之旅!從今天開始,我將每天堅持閱...
    李薇帆閱讀 2,282評論 1 4
  • 似乎最近一直都在路上,每次出來走的時候感受都會很不一樣。 1、感恩一直遇到好心人,很幸運。在路上總是...
    時間里的花Lily閱讀 1,791評論 1 3
  • 1、expected an indented block 冒號后面是要寫上一定的內(nèi)容的(新手容易遺忘這一點); 縮...
    庵下桃花仙閱讀 1,159評論 1 2
  • 一、工具箱(多種工具共用一個快捷鍵的可同時按【Shift】加此快捷鍵選取)矩形、橢圓選框工具 【M】移動工具 【V...
    墨雅丫閱讀 1,826評論 0 0

友情鏈接更多精彩內(nèi)容