iT邦幫忙

2025 iThome 鐵人賽

DAY 7
0
Modern Web

從 React 學 Next.js:不只要會用,還要真的懂系列 第 7

【Day 7】Hydration 失敗了!? - Next.js 常見錯誤 Hydration Mismatch

  • 分享至 

  • xImage
  •  

昨天我們已經看了什麼是 Hydraion 了,今天接著來看在 Next.js 框架中和 Hydration 有關聯的一種常見錯誤。

https://ithelp.ithome.com.tw/upload/images/20250907/20130914efxt0vOrao.png
看錯誤內容應該不難理解這是和 Hydration 相關的錯誤,裡面的說明也很明確地提到了「the server rendered text didn't match the client.」伺服器渲染的文字和 client 端不同,這個錯誤也就是所謂的「Hydrarion Mismatch」error。

什麼是 Hydration Mismatch?

首先,我們一樣先來了解什麼是「Hydration Mismatch」。

將這個詞彙直接翻成中文,就是「水合不匹配」的意思,白話一點也就是在 Hydration 的過程中發現 response 回來的 HTML 和 React 解析 React Component 產生的 virtural DOM 不一致。這個錯誤只會出現在 SSR 的模式下,因為只有 SSR 的模式會在一開始進入頁面時,就從伺服器的 response 取得 HTML,也只有 SSR 模式會有 Hydration 的過程。

簡單來說,Hydration Mismatch 就是 Server HTML 和 Client Virtual DOM 不同時產生的錯誤

那為什麼會發生 Hydration mismatch 呢?我們先來快速回顧一下 Hydration 的過程。

中場回顧 Hydration 的過程

在進行 Hydration 的過程,主要會經歷這段流程:

瀏覽器取得伺服器 response 的 HTML → JavaScript 加載完成 → React 將 React Component 解析成 virtual DOM (Fiber Tree) → 把 response 回來的 HTML 和 React 解析的 virtudal DOM 進行比對 → 確認沒有不一致的狀況後,進行事件的綁定 → React 進行元件狀態的初始化,接著執行 effect。

整體看下來,Hydration 的重點就是確認伺服器端 response 的 HTML 和 React 在瀏覽器建立的元件樹是否一致,才進一步綁定事件。只要這兩邊有落差,就會導致 Hydration Mismatch 的狀況出現,並且跳出 Hydration Mismatch error。

為什麼會發生 Hydration Mismatch?

從前面的快速回顧 Hydration 的過程,可以知道在 Hydration 的過程中,會先進行伺服器 response 的 HTML 和 React 解析的 virtual DOM 的比對,就可以知道當 Hydration Mismatch error 出現時,就是在這個步驟出了問題。

但可能這個時候會有人有這個疑問「伺服器回傳的 HTML 和 React 解析出的 virtual DOM 不都是使用同一份檔案產生出來的結果嗎?為什麼還有可能會出現不一致的狀況呢?」

不管是在伺服器回傳的 HTML 還是 React 解析的 virtual DOM 的確使用的都是同一份檔案,但是這兩個產生出來的內容,有兩個差異,那就是 「產生的地方不同」 以及 「產生的時間點不同」,而這兩個部分也就會造成最後結果出現不同。

我們再用幾個實際的例子來思考看看!
• 例子1:有一個畫面會使用到只有瀏覽器上會有的 window,或是 localStorage 來判斷要怎麼顯示畫面。

例如:

"use client";
const DisplayButton = () => {
  // 從 localStorage 取得 token 來判斷是否是登入狀態
  const isLogin = typeof localStorage !== "undefined" && localStorage.getItem("token");


  return (
    <div>
      {/* 依照是否為登入狀態顯示不同的按鈕 */}
      {isLogin ? <button>查看詳細內容</button> : <button>登入</button>}
    </div>
  
  )
}

export default DisplayButton

在這個例子中,應該不難想像會發生什麼樣的狀況。由於 window 是瀏覽器的全域物件,localStorge 則是瀏覽器提供的 API,這兩者都只能在瀏覽器上才有辦法使用,所以一開始先在伺服器上產生完整的 HTML 時,永遠都不會取得正確的值,到了瀏覽器上才有辦法取得正確的值。這樣的狀況,也就會造成在伺服器先產生的 HTML 和 React 解析的 virtual DOM 有差異。只要伺服器首次返回的 HTML 與瀏覽器 Hydration 時的首次 render 結果不同,就可能出現 mismatch error。

• 例子 2:在畫面上直接使用時間
另一個例子是在畫面中直接使用 new Date 來顯示時間,如下這樣

"use client";
const Timer = () => {
  return (
    <div>
      <p>{new Date().toString()}</p>
    </div>
  );
};

export default Timer;

在這個例子中,直接在畫面中使用了 new Date 來顯示時間,加上了 "use client" 讓元件變成了 Client Component,使得這個元件會在伺服器產生一次 HTML,在瀏覽器上還會再解析 virtual DOM 來比對來進行 Hydration,但是在伺服器產生 HTML 的時間點和 React 解析的 virtual DOM 上時間不同,所以顯示的時間就會有差異,進而導致在伺服器上產生的 HTML 和 React 解析的 virtual DOM 上時間有差異。

如何避免 Hydration Mismatch?

接著我們來看一下要如何避免 Hydration Mismatch。
簡單來說就是 「避免寫一些伺服器上和瀏覽器上會出現不同結果的邏輯」,但是在一個完整且功能眾多的專案中,一定會有不得不使用一些 window 或是 localStorage 時,或是一定需要用到時間等內容時,該怎麼做呢?

那就是將這些邏輯處理的部分,往後移動到 Hydration 的流程結束後進行。這時候就可以使用 useEffect 來處理。

我們實際用前面提到的例子來調整!
• 例子 1:

"use client";
const DisplayButton = () => {
  // 用 useState 建立一個元件的 state 並且用 useEffect 設定這個 state 值
  const [isLogin, setIsLogin] = useState(false);
  useEffect(() => {
    if (typeof localStorage !== "undefined") {
      setIsLogin(localStorage.getItem("token") !== null);
    }
  }, []);

  return (
    <div className="flex">
      {/* 依照是否為登入狀態顯示不同的按鈕 */}
      {isLogin ? <button>查看詳細內容</button> : <button>登入</button>}
    </div>
  );
};

export default DisplayButton;

這裡使用 state 和 useEffect 去延後畫面會因為邏輯而變動的時間點,以避免伺服器上渲染的 HTML 和在瀏覽器上 React 首次渲染出的 virtualDOM 出現差異。

• 例子 2:

"use client";

import { useEffect, useState } from "react";

const Timer = () => {
  const [time, setTime] = useState(null);
  useEffect(() => {
    setTime(new Date().toString());
  });

  return (
    <div>
      <p>{time}</p>
    </div>
  );
};

export default Timer;

在第二個例子中一樣也是把 new Date 的值,和設定的時機點往後延到初始化 state 和執行 useEffect 的階段,讓伺服器上渲染的 HTML 和在瀏覽器上 React 首次渲染出的 virtual DOM 能夠維持一致。

總結來說,就是在寫一些邏輯時,要盡可能地去思考伺服器上的渲染和瀏覽器上的初次渲染會不會有差異。

總結

最後再來做一個快速的小總結!
所謂的 Hydration Mismatch,指的是在 SSR 模式下,伺服器回傳的 HTML 與瀏覽器端 React 在 Hydration 過程中第一次 render 的結果不一致時,React 會拋出警告。這種不一致可能導致 React 無法順利接管 DOM,甚至會影響事件綁定或互動行為。
為了避免這樣的 error 出現,常見的做法是把依賴瀏覽器環境、會影響畫面的邏輯延後到 useEffect 再執行,或者把判斷值存在伺服器與瀏覽器都能取得的來源(像是 cookie、headers),讓兩邊產出的結果一致。

今天的 Hydration Mismatch 的部分就到這裡,接下來會來看前面有提到過的 Streaming。


上一篇
【Day 6】讓靜態頁面活起來的關鍵 - Hydration
下一篇
【Day 8】在 SSR 下,讓畫面分批顯示的魔法 - Streaming
系列文
從 React 學 Next.js:不只要會用,還要真的懂11
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言