今天我們要將視角從「純前端 CSR (Client-Side Rendering)」拉高到伺服器端與渲染範式的演進史,並解答前端最常聽到卻也最抽象的概念:Hydration(水合)。
前端渲染架構的演進,本質上是在「SEO / 首屏速度 (FCP)」 與 「伺服器負擔 / 內容即時性」 之間尋找最佳平衡點:
[CSR] ──► [SSR] ──► [SSG] ──► [ISR]
傳統 CSR:空白 HTML,等待 JS 下載繪製
SSR:伺服器即時渲染 HTML,SEO 友善但伺服器負擔高
SSG:建置階段 (Build-time) 預先產生 HTML,速度極快但無法即時更新
ISR:結合兩者,靜態頁面支援背景按需重新產生 (Revalidate)

在 SSR 或 SSG 的流程中,伺服器會先傳送一份已經包含完整文字與 DOM 結構的 HTML 給瀏覽器。使用者此時能「看到」畫面(首屏渲染快),但此時的 HTML 只是靜態的乾涸骨架,無法進行點擊、輸入或觸發任何狀態變更。
水合的三步驟:
伺服器端 (Server):執行組件渲染,輸出 HTML 字串與 state 快照傳給瀏覽器。
瀏覽器下載 JS (Client):下載並解析 React JS Bundle。
Hydrate 過程:React 不重新創建 DOM 節點,而是遍歷現有的 HTML DOM 樹,將 Event Listeners(如 onClick)附著上去,並綁定對應的 Fiber 節點與 State。
import { hydrateRoot } from 'react-dom/client';
// 將 React 的交互邏輯「水合」至伺服器已渲染好的 HTML 容器中
hydrateRoot(document.getElementById('root'), <App />);
這是開發 SSR/SSG 時最常發生的警告。當伺服器渲染出的 HTML 結構與客戶端初次 Re-render 的 Virtual DOM 結構不一致時,React 就會拋出語法錯誤。
常見引發原因:
使用了特定於瀏覽器的動態數據:例如在組件中直接寫死 window.innerWidth、localStorage 或 new Date().toLocaleTimeString()(伺服器與客戶端時間不同步)。
不合法的 HTML 嵌套結構:例如在 <p> 標籤內包裹<div>,導致瀏覽器原生的 HTML Parser 自動做修正,破壞了原本的 DOM 結構。
// 錯誤範例:伺服器端輸出 Time A,客戶端水合時變成 Time B
function Clock() {
return <span>現在時間:{new Date().toLocaleTimeString()}</span>;
}
// 解法:確保水合階段與伺服器一致,掛載完成後 (useEffect) 再更新為客戶端時間
function Clock() {
const [time, setTime] = useState(null);
useEffect(() => {
setTime(new Date().toLocaleTimeString());
}, []);
return <span>現在時間:{time ?? '載入中...'}</span>;
}