學到這裡,你已經會用 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>
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 />);