學到這裡,你已經會用 Hook 寫出能動的 React 介面。但面試、讀文件、跟後端討論架構時,常會聽到 SPA、CSR、SSR、Hydration 這些詞——它們描述的不是「怎麼寫 useState」,而是 React 應用程式怎麼被送到瀏覽器、怎麼變成畫面。

本章將把這些名詞一次講清楚,並連接之前學過的 Virtual DOM、Mount、useEffect。更深入的 Hook 機制請參考 React 的運作原理 閱讀。


打開一個網頁,背後到底發生了啥?

想像你點開一個商品頁,有三種常見體驗:

體驗 你看到的 背後可能是
傳統多頁網站 整頁白一下 → 全新頁面出現 MPA — 每次換頁都向伺服器要一份完整 HTML
純前端 SPA(CRA) 先看到空白或 loading → 資料到了才出現列表 CSR — 瀏覽器下載 JS bundle,在客戶端 render
Next.js 全端 幾乎立刻看到內容,按鈕仍可互動 SSR / SSG — 伺服器先給 HTML,再 Hydration 接手互動

中心的 Next.js 專案屬於第三種;CodeSandbox 純 React 練習則接近第二種。兩者寫法都是 React,但 「誰、在哪裡、何時產生 HTML」 完全不同。


名詞地圖

flowchart TB
  subgraph 伺服器
    SSR[SSR / SSG 產生 HTML]
    API[API / DB]
  end
  subgraph 瀏覽器
    HTML[收到 HTML]
    JS[下載 JS Bundle]
    HY[Hydration 綁定事件]
    CSR[CSR 繼續更新畫面]
  end
  API --> SSR
  SSR --> HTML
  HTML --> JS
  JS --> HY
  HY --> CSR

核心縮寫對照

名詞 全名 / 意思 一句話 你學過的專案
MPA Multi-Page Application 每個 URL 對應一份獨立 HTML,換頁 = 整頁重載 傳統 PHP / 靜態網站
SPA Single Page Application 只載入一次殼,換「頁」其實是換 Component,不刷新整頁 中心 Next.js、CRA 都是 SPA 架構
CSR Client-Side Rendering HTML 幾乎是空的,由瀏覽器執行 JS 後才畫出 UI CRA、useEffect 打 API 後 setState
SSR Server-Side Rendering 伺服器先跑 React,把完整 HTML 傳給瀏覽器 Next.js getServerSideProps、App Router Server Component
SSG Static Site Generation 建置時就產好 HTML,部署後直接當靜態檔送出 Next.js getStaticProps、部落格頁
ISR Incremental Static Regeneration SSG + 定時在背景重新產生,兼顧速度與新鮮度 Next.js revalidate
Hydration (注水) SSR/SSG 的 HTML 已經在那了,React 在瀏覽器「認領」DOM 並綁上事件 Next.js 首屏可點擊的原因
RSC React Server Component 元件在伺服器執行,只把結果(非完整 JS)傳給客戶端 App Router 預設元件(見前端開發首頁講義)

<aside> ⚛️

SPA ≠ CSR。 SPA 是「架構形態」(單頁殼 + 前端路由);CSR 是「誰負責第一次畫面」。SPA 可以 CSR(CRA),也可以 SSR(Next.js 先給 HTML 再 Hydration)。

</aside>


三種專案,同一套 React 語法

純 React(CRA)— 典型 CSR + SPA

npx create-react-app my-app --template typescript
npm start

<aside> ⚠️

create-react-app 已在 2025 年 2 月被 React 官方宣布棄用(deprecated),新專案官方建議改用 Next.js 等框架,或 Vite 這類建置工具。這裡只是拿它當「純 CSR + SPA」的例子。參考:Sunsetting Create React App

</aside>

<!-- public/index.html — 幾乎空的殼 -->
<div id="root"></div>
// src/index.tsx — 程式入口
const root = ReactDOM.createRoot(document.getElementById('root')!);
root.render(<App />);