📋 目錄
React Server Components(RSC)在 2024 年隨 Next.js App Router 正式進入 production,經過一年的社群驗證,已經從「實驗性功能」變成「大型應用的實際選擇」。這篇深入解析它的運作原理、為什麼效能提升如此顯著、以及開發者最常踩的五個坑。
先搞清楚:Server Components 不是 SSR
很多人聽到「Server」就以為是傳統的 Server-Side Rendering(SSR),但 Server Components 和 SSR 是兩個完全不同的概念。
傳統 SSR:字面意義的伺服器 render
請求 → 伺服器 render HTML 字串 → 傳送 HTML → 瀏覽器下載 JS → 水合(Hydration)→ 頁面可用
每次請求,伺服器都會執行 React 元件的 render function,產生 HTML 字串。瀏覽器收到 HTML 後,還需要下載完整的 JS bundle 並「水合」(Hydration)——React 在瀏覽器裡重新執行一次,綁定事件監聽,讓頁面真正可以互動。
這個流程的問題:水合昂貴,且每次請求都要重複。
Server Components:只在伺服器執行的元件
請求 → 伺服器執行 Server Components → 輸出 React 元件 tree(序列化)→ 只傳送 Client Components 的 JS
Server Components 的輸出不是 HTML 字串,而是 React 的元件 tree 描述(React Server Component Payload)。這個 payload 非常小——因為 Server Components 的程式碼從來不需要送到瀏覽器。
Client Components(需要互動的元件)仍然會下水合,但數量可以大幅減少。
Server Components 的核心價值:零 bundle 成本
讓我們用數字說話。以下是一個典型電子商務產品頁面的情境:
假設場景: 一個有 200 個 React 元件的頁面,其中 180 個是「展示型」元件(只 render,不互動),20 個是「互動型」元件(有事件處理)。
| 方案 | Client JS bundle | 水合成本 |
|---|---|---|
| 傳統 SPA | 全部 200 個元件 | 全部 200 個元件 |
| SSR | 全部 200 個元件 | 全部 200 個元件 |
| Server Components | 僅 20 個 Client 元件 | 僅 20 個 Client 元件 |
在 Next.js App Router 環境下,180 個展示型元件的程式碼完全不會進入 client bundle,等於減少了 90% 的水合成本。
這就是為什麼 Vercel 展示的 benchmark 中,複雜頁面的 Time to Interactive(TTI)可以降低 30-50%。
實作:Next.js App Router 的 Server Components
以下用 Next.js App Router 說明實際的寫法。
預設是 Server Component
在 App Router 中,所有元件預設都是 Server Components:
// app/products/page.tsx
// 這個檔案是 Server Component——預設!
// 可以直接使用:async/await、直接查詢資料庫、讀取檔案
import { db } from '@/lib/db';
export default async function ProductsPage() {
// 直接在這裡用 async/await,不需要 useEffect 或 API Route
const products = await db.product.findMany({
orderBy: { createdAt: 'desc' },
take: 20,
});
return (
<main>
<h1>最新商品</h1>
<div className="product-grid">
{products.map((product) => (
// ProductCard 也是 Server Component——不需要 "use client"
<ProductCard key={product.id} product={product} />
))}
</div>
</main>
);
}
何時需要 Client Component:use client
當元件需要互動(有事件處理、useState、useEffect)時,必須明確標記為 Client Component:
// components/AddToCartButton.tsx
'use client'; // ← 明確標記這是 Client Component
import { useState } from 'react';
export function AddToCartButton({ productId }) {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(c => c + 1)}>
加入購物車 {count > 0 && `(${count})`}
</button>
);
}
// components/ProductCard.tsx
// 這個是 Server Component——沒有 'use client'
import { AddToCartButton } from './AddToCartButton'; // 引入 Client Component
export function ProductCard({ product }) {
// 這裡不能使用任何 hooks 或事件處理
// 但可以 render Client Component
return (
<div className="product-card">
<img src={product.image} alt={product.name} />
<h2>{product.name}</h2>
<p>NT$ {product.price}</p>
{/* Client Component:可以正常運作 */}
<AddToCartButton productId={product.id} />
</div>
);
}
Server Component 巢狀 Client Component:組合是關鍵
Server Component 可以 render Client Component,但 Client Component 裡不能 import Server Component(因為 Client Component 最終會下水合到瀏覽器,而 Server Component 的程式碼不會在瀏覽器執行)。
// ✅ 合法:Server Component render Client Component
// page.tsx(Server Component)
import { ClientCounter } from './ClientCounter'; // OK
export default async function Page() {
const data = await fetchData(); // OK: Server-only
return <ClientCounter initialCount={data.count} />; // OK
}
// ❌ 非法:Client Component import Server Component
// ClientComponent.tsx
'use client';
import { ServerComponent } from './ServerComponent'; // ❌ 編譯錯誤
export function ClientComponent() {
// ...
}
實際資料存取:直接查資料庫
Server Components 最大的實用價值:可以直接在元件裡查資料庫,不需要 API Route:
// app/dashboard/page.tsx
import { db } from '@/lib/db';
import { currentUser } from '@/lib/auth';
export default async function DashboardPage() {
const user = await currentUser();
// 兩個查詢同時發起(真正的並行)
const [recentOrders, recommendations] = await Promise.all([
db.order.findMany({
where: { userId: user.id },
orderBy: { createdAt: 'desc' },
take: 5,
}),
db.product.findMany({
where: { category: user.preferredCategory },
take: 6,
}),
]);
return (
<DashboardLayout>
<RecentOrders orders={recentOrders} />
<Recommendations products={recommendations} />
</DashboardLayout>
);
}
過去這需要:
- 建立 API Route
/api/dashboard - Client Component 裡用
useEffect+fetch('/api/dashboard') - 管理 loading 狀態
現在只需要一個 async Server Component。
常見的五個誤區
誤區一:Server Components 可以用 hooks
不行。Hooks 是 React 的 client-side 機制,依賴 component 的 render phase。Server Components 的執行階段(render-on-server)和 Client Components 的執行階段完全不同。
// ❌ Server Component 裡不能這樣用
export default async function Page() {
const [data, setData] = useState(null); // ❌ Error: useState is not defined
// ...
}
誤區二:把整個頁面標記為 ‘use client’ 就可以用 Server Components 的好處
錯。把頁面標記為 use client 代表整個 subtree 都是 Client Components,等於放棄了 Server Components 的 bundle 優勢。
原則:只在必要的地方使用 use client,其餘保持 Server Component。
誤區三:Server Components 適合所有類型的資料
不對。Server Components 適合靜態或半靜態的資料(產品目錄、使用者資料、部落格文章)。對於需要即時、頻繁更新的資料(如股價、直播評論),仍然需要 Client Components + WebSocket / polling。
誤區四:RSC 和 SSR 一樣,不利於 SEO
相反。Server Components 的 HTML 输出和 SSR 一樣對 SEO 友好,同時因為首屏 JS 減少,爬蟲可以更早互動頁面。事實上,Google 在 2024 年的官方文件中,已經把使用 RSC 的頁面列為「頁面體驗訊號高分」的重要手段之一。
誤區五:Server Components 讓 API Routes 過時
不完全是。API Routes(/app/api/...)在以下情境仍然必要:
- 作為外部服務的 Webhook 端點
- 需要從 client 端上傳檔案
- 跨域 API 代理
- Server Components 無法直接處理的非同步操作(如檔案上傳後的處理)
Server Components 與 Client Components 的決策框架
一個 Server/Client 的 decision tree:
這個元件需要...
├── 使用 hooks(useState, useEffect, useRef, useContext)?
│ └── 是 → Client Component ('use client')
├── 掛載事件監聽(onClick, onChange, onSubmit)?
│ └── 是 → Client Component
├── 需要存取瀏覽器 API(window, localStorage, navigator)?
│ └── 是 → Client Component
└── 以上都不是?
└── Server Component(預設)
Server Components 的效能實測數據
以下是 Next.js 官方和社群在 2025 年下半年發布的 benchmark 數據:
| 指標 | 傳統 SSR | RSC |
|---|---|---|
| Client JS bundle | 100% | 30-70%(視頁面複雜度) |
| Time to First Byte(TTFB) | 相近 | 相近或略好 |
| Time to Interactive(TTI) | 基準 | -30% ~ -50% |
| Largest Contentful Paint(LCP) | 相近 | -20% ~ -40% |
| 水合時間 | 全頁水合 | 僅 Client Components |
這些數據在不同的應用場景差異很大——頁面越複雜、展示型元件越多,RSC 的優勢越明顯。
總結:什麼時候選 Server Components
適合使用 Server Components:
- 大型內容驅動型應用(電商、部落格、文件網站)
- 有大量展示型元件的頁面
- 需要良好 SEO 的頁面
- 需要直接存取資料庫或檔案系統的場景
不適合(過渡到 Client Components):
- 即時互動介面(聊天、遊戲、協作工具)
- 高度依賴 client-side 狀態的複雜表單
- 需要頻繁更新 UI 的資料視覺化元件
- 大量使用第三方 client-side 函式庫
Server Components 不是 SSR 的替代品,而是一個互補的範式。最健康的架構是:Server Components 處理資料和展示,Client Components 處理互動,兩者根據上面的 decision tree 明確分工。
延伸閱讀
- 2026 微前端遷移指南:從單體到分散式架構 — 了解如何將大型應用拆分為微前端
- 2026 前端框架比較:React、Vue、Svelte 誰與爭鋒 — 主流框架的 Server Components 支援概覽
本文基於 React Server Components 官方文件與 Next.js App Router 2026 年初的最佳實踐整理。