客戶端渲染 vs 伺服器端渲染

客戶端渲染 vs 伺服器端渲染

核心差異

誰負責把 JSX 轉換成使用者看到的 HTML?


流程比較

SSR(伺服器先渲染)

瀏覽器:GET /posts
  ↓
伺服器:
  ├─ 執行 src/app/posts/page.tsx
  ├─ 組合 layout.tsx + page.tsx
  ├─ 轉換成 HTML 字串
  └─ 回傳給瀏覽器
  ↓
瀏覽器:收到完整的 HTML
  ├─ 立即顯示
  └─ 再載入 JavaScript 處理互動

使用者立即看到內容。

CSR(客戶端稍後渲染)

瀏覽器:GET /index.html
  ↓
伺服器:回傳空的 HTML + JavaScript 程式碼
  ↓
瀏覽器:
  ├─ 下載 JavaScript(3-5 秒)
  ├─ 執行 React 程式碼
  ├─ 發送 fetch(/api/posts)
  ├─ 等待資料回傳
  └─ 渲染頁面
  ↓
使用者終於看到內容(5-10 秒)

使用者先看到讀取畫面。


這個專案的範例

SSR 版本(Next.js 預設)

// src/app/posts/page.tsx
// 沒有 "use client" = 伺服器端渲染

export default function PostList() {
  const { tableProps } = useTable();
  
  return <Table {...tableProps} />;
}

發生的事:

1. 使用者造訪 /posts
2. 伺服器執行 page.tsx
3. useTable 在伺服器上呼叫 API,取得資料
4. 伺服器把 <Table data={[...]} /> 轉成 HTML
5. 瀏覽器收到:<html><body>...<table>...</table></body></html>
6. 頁面立即顯示

CSR 版本(加上 "use client")

"use client";  // ← 切換成客戶端渲染

import { useState, useEffect } from "react";

export default function PostList() {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // 在瀏覽器抓資料
    fetch("/api/posts")
      .then(r => r.json())
      .then(setPosts)
      .finally(() => setLoading(false));
  }, []);

  if (loading) return <div>載入中...</div>;
  return <Table dataSource={posts} />;
}

發生的事:

1. 使用者造訪 /posts
2. 伺服器回傳:<html><body><div>載入中...</div></body></html>
3. 瀏覽器顯示「載入中...」
4. JavaScript 載入完成,useEffect 執行
5. 瀏覽器發送 fetch(/api/posts)
6. 資料回傳,setPosts 更新狀態
7. 頁面重新渲染,表格顯示出來

使用者先看到讀取畫面,等 5-10 秒才看到表格。


比較表

指標 SSR(伺服器端渲染) CSR(客戶端渲染)
使用者看到內容的時間 快(秒級) 慢(5-10 秒)
伺服器負擔 重(每次請求都要執行程式碼) 輕(只送 HTML + JS)
SEO ✅ 好(搜尋引擎讀得到 HTML) ❌ 差(搜尋引擎看不到內容)
互動延遲 快(JS 已載入) 慢(要等 JS 下載完)
頻寬消耗 較多(每次請求伺服器都要處理) 較少(瀏覽器做大部分工作)
可快取 ✅ 可以(靜態 HTML) ❌ 較難(動態 JS)
離線可用 ❌ 不行 ✅ 可以

何時該用哪一種

使用 SSR

適合:

不適合:

使用 CSR

適合:

不適合:


Next.js 的策略

Next.js 預設使用 SSR,除非你加上 "use client"

預設 = SSR
     ↓
加上 "use client" = CSR

這一行就決定了一切:

"use client";  // ← 加上這行 = 客戶端渲染

這個專案目前的作法

src/app/layout.tsx                ← SSR(伺服器渲染 HTML 外殼)
  <html>
    <Providers>
      {children}
    </Providers>
  </html>

src/app/providers.tsx            ← CSR(有 "use client")
  <Refine dataProvider={...} />

src/app/posts/page.tsx           ← CSR(有 "use client")
  <useTable />

src/app/api/posts/route.ts       ← 純伺服器端(API 路由)
  pool.query(...)

混合模式:兩種方式的優點兼具。


為什麼 Next.js 這樣設計

Next.js 讓你能針對應用程式的不同部分分別選擇

這樣就不必再像過去那樣,整個應用程式只能二選一。


不只 SSR 與 CSR

SSR 與 CSR 其實只是其中一個維度而已。現代框架(包含 Next.js)還混合了幾種其他策略:

SSG(Static Site Generation,靜態網站產生)

ISR(Incremental Static Regeneration,增量式靜態再生)

export const revalidate = 60; // 最多每 60 秒重新產生一次

Streaming SSR(串流式伺服器渲染)

RSC(React Server Components,伺服器元件)

Islands / Resumability(島嶼架構 / 可續行架構)


總結

渲染方式 誰做 何時 取捨
SSR 伺服器建構 HTML 應用程式啟動時 伺服器要費力氣
CSR 瀏覽器建構 HTML JS 載入完成後 使用者要等待
混合(Next.js) 兩者配合,依元件個別選擇 依需求判斷 最佳使用體驗

Next.js 讓 SSR 變得很簡單,因為它是預設值。如果需要 CSR,加上 "use client" 就好。


延伸:何時該混合使用

推薦模式:

// 外殼 — SSR(資料量大或需要 metadata)
export default function RootLayout({ children }) {
  const config = await fetchConfig();  // ← 伺服器端
  return (
    <html>
      <body>{children}</body>
    </html>
  );
}

// 頁面 — CSR(需要互動)
"use client";
export default function Page() {
  const [state, setState] = useState();  // ← 瀏覽器端
  return <div>{state}</div>;
}

這樣使用者既能快速看到頁面(SSR),又能享有流暢的互動體驗(CSR)。


延伸閱讀