想搞懂為什麼你的 Next.js 專案總是出現 react-hydration-error 嗎?這篇文章深入解析 Hydration 的原理,帶你了解 Next.js 的 SSR 與 CSR 流程,並提供三種避免 Hydration Error 的實用解法,讓你的 React 頁面更穩定、無錯誤
Written by: Chia1104 CC BY-NC-SA 4.0

過去大家在開發 NextJS 的專案時一定看過這類錯誤 react-hydration-error,在說明這問題如何解決前我們要先了解 NextJS 是如何做 SSR (Server-Side Rendering) 跟 CSR (Client-Side Rendering) 的。
我們都知道 NextJS 是一個 React 的全端框架,用 React 的方式做到 SSR。
但 NextJS 跟傳統全端框架 - 如:PHP 的 Laravel 比較不一樣的點是 他只會在使用者第一次進到的頁面 預生成 HTML 來做 SSR,後續我們再做其他頁面的導覽時就跟傳統 SPA (Single-Page Application) 的 CSR 概念一樣,只把我們需要的元件載入進來以防止整個頁面的重載 - NextJS 用戶端導頁。
React 在伺服器端有對應的 API 來生成 HTML,其中一個是 renderToPipeableStream。
此方法的調用可以使 React 逐步生成 HTML 並透過 Node.js Stream,將 HTML 串流到客戶端。
import { renderToPipeableStream } from 'react-dom/server';
const { pipe } = renderToPipeableStream(<App />, {
bootstrapScripts: ['/main.js'],
onShellReady() {
response.setHeader('content-type', 'text/html');
pipe(response);
}
});流程大概如下:
剛剛說第一次渲染的是 HTML 那他要怎麼跟 React 互動,又換一個方式說:我們要怎麼透過這個 HTML 讓我們有辦法透過 React 的 API 如 useState 來做互動。
在 Server 端完成 SSR,將靜態 HTML 傳送至客戶端後,React 可以透過 hydrateRoot 將這些靜態的 HTML 轉換為可互動的 React 元件。
const root = hydrateRoot(domNode, reactNode)domNode: 對應到 HTML 當中的跟節點元件 (SSR 預生成的 HTML)。reactNode: 為生成對應 HTML 的 React 元件。import { hydrateRoot } from "react-dom/client";
import App from "./App";
hydrateRoot(document.getElementById("root"), <App />);這個將伺服器預先渲染好的靜態 HTML,與客戶端的 React 應用程式結合,並賦予其互動性的過程,就稱為 Hydration
從 hydrateRoot 這方法來看,這是在客戶端做畫面渲染所引用的,而 NextJS 在做 SSR pre-render 時也會用 renderToPipeableStream 做 HTML 預生成。
hydrateRoot 的預期內容必須與預渲染的 HTML 完全一致,不然就會導致 react-hydration-error
從剛才的流程來看,React 為了確保伺服器跟客戶端所生成的 HTML 一樣,在 hydrateRoot 的時候若有不一樣就會產生 Hydration Error。
通常最常見的 Hydration Error 大概就是時間的處理了,例如時區:通常伺服器端的時區多半是 +0 的 UTC 時區,但使用者端一定是跟者瀏覽器的時區走。

除了時間之外,若是我們直接用瀏覽器端的 API 如 localStorage 等也會造成 Hydration Error,因為伺服器端沒有 localStorage。
useEffect 判斷伺服器或客戶端"use client";
import { useState, useEffect } from 'react'
export default function App() {
const [isClient, setIsClient] = useState(false)
useEffect(() => {
setIsClient(true)
}, [])
return <h1>{isClient ? 'This is never prerendered' : 'Prerendered'}</h1>
}因為 useEffect 是基於瀏覽器的環境運行的,在伺服器端及客戶端的 DOM tree 中的初始值 (isClient) 永遠是 false,進而避免 Hydration Error。
主要確保伺服器端及客戶端的初始值一樣即可。
dynamic SSR 為 false可以透過 NextJS 的 dynamic API 來標注哪個元件不需要做 pre-render,在伺服器跟客戶端的 hydrateRoot 都不做生成。
import dynamic from 'next/dynamic'
const NoSSR = dynamic(() => import('../components/no-ssr'), { ssr: false })
export default function Page() {
return (
<div>
<NoSSR />
</div>
)
}suppressHydrationWarning該方法會取消 Hydration Error 的警告,過去在用 next-themes 這套件的時候應該會設定到,如果我們會需要客戶端動態調整某個 DOM 元件的參數,由於伺服器端沒這個值進而導致的 Hydration Error,就可以用該方法來靜音。
<html colorSchema="dark" suppressHydrationWarning />