React Server Components 完整教學:RSC 原理解析與實戰指南
如果你最近有在關注 React 生態系,應該會發現 React Server Components(RSC)這個詞出現的頻率越來越高。老實說,我第一次看到這個概念的時候也是一頭霧水——Server Components?React 不是前端框架嗎,怎麼跟 Server 扯上關係了?
但實際用了之後,我只能說:RSC 真的改變了我對 React 應用程式架構的理解。今天這篇 React Server Components 教學,我會從最基本的概念講起,搭配實際的程式碼範例,讓你徹底搞懂 RSC 到底是什麼、為什麼需要它、以及怎麼在專案中使用。
什麼是 React Server Components?
簡單來說,React Server Components 是一種只在伺服器端執行的 React 元件。跟傳統的 SSR(Server-Side Rendering)不同,RSC 不會把元件的 JavaScript 程式碼送到瀏覽器端。這代表什麼?代表你的 bundle size 可以大幅縮減。
想像一下,你有一個元件需要用到一個超大的 markdown 解析套件來渲染文章內容。在傳統做法裡,這個套件會被打包進 client bundle,使用者得下載它。但如果用 Server Component,這個套件只存在於伺服器端,使用者完全不用下載——他們只拿到最終的 HTML 結果。
RSC 的核心概念可以這樣理解:
- Server Components:在伺服器上渲染,不會送 JS 到客戶端,可以直接存取資料庫、檔案系統
- Client Components:傳統的 React 元件,在瀏覽器執行,處理互動邏輯和狀態管理
- Shared Components:可以在兩端運行的元件,但有一定限制
RSC 跟 SSR 有什麼不同?
這是很多人搞混的地方,我自己當初也花了一些時間才理清。讓我用一張比較表來說明:
| 特性 | SSR | RSC |
|---|---|---|
| JS 送到客戶端 | 是(hydration 需要) | 否(零 JS bundle) |
| 可使用 useState/useEffect | 是(hydration 後) | 否 |
| 直接存取後端資源 | 僅限 getServerSideProps | 元件內直接 await |
| 串流渲染 | 部分支援 | 原生支援 |
| 重新渲染 | 整個元件樹 | 可局部更新 |
最關鍵的差異在於:SSR 雖然在伺服器端先渲染了 HTML,但最終還是會把完整的 JavaScript 送到客戶端做 hydration。RSC 則是根本不送 JavaScript——Server Component 的程式碼永遠留在伺服器上。
在 Next.js 中使用 RSC
目前最成熟的 RSC 實作就是 Next.js 的 App Router。如果你還在用 Pages Router,強烈建議參考 Next.js App Router 遷移教學 來升級。在 App Router 中,所有元件預設都是 Server Components,你得主動加上 'use client' 才會變成 Client Component。
來看一個基本的 Server Component 範例:
// app/posts/page.tsx — 這是一個 Server Component
// 注意:不需要加任何特殊標記,預設就是 Server Component
import { db } from '@/lib/database';
async function PostsPage() {
// 直接在元件裡面 await 資料庫查詢,超方便!
const posts = await db.post.findMany({
orderBy: { createdAt: 'desc' },
take: 20,
});
return (
<main>
<h1>最新文章</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</li>
))}
</ul>
</main>
);
}
export default PostsPage;
看到了嗎?不需要 useEffect、不需要 useState、不需要任何 data fetching 的 library。直接 await 就對了。這就是 RSC 最讓人驚豔的地方——資料取得變得極其直覺。
Client Component 與 Server Component 的搭配
當然,不是所有東西都能放在 Server Component 裡。只要涉及到使用者互動(像是點擊、輸入、狀態管理),你就需要用 Client Component。重點是要學會怎麼把兩者搭配好。
一個常見的模式是:Server Component 負責取資料,Client Component 負責互動:
// components/LikeButton.tsx — Client Component
'use client';
import { useState } from 'react';
export function LikeButton({ postId, initialCount }: {
postId: string;
initialCount: number;
}) {
const [count, setCount] = useState(initialCount);
const [isLiked, setIsLiked] = useState(false);
const handleLike = async () => {
setIsLiked(!isLiked);
setCount(prev => isLiked ? prev - 1 : prev + 1);
await fetch(`/api/posts/${postId}/like`, { method: 'POST' });
};
return (
<button onClick={handleLike}>
{isLiked ? '❤️' : '🤍'} {count}
</button>
);
}
// app/posts/[id]/page.tsx — Server Component
import { db } from '@/lib/database';
import { LikeButton } from '@/components/LikeButton';
async function PostPage({ params }: { params: { id: string } }) {
const post = await db.post.findUnique({
where: { id: params.id },
});
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
{/* Server Component 傳資料給 Client Component */}
<LikeButton postId={post.id} initialCount={post.likeCount} />
</article>
);
}
這個模式非常重要——讓 Server Component 當「資料提供者」,Client Component 當「互動處理者」。這樣既能享受 RSC 的零 bundle 優勢,又不會犧牲使用者體驗。
RSC 的資料取得模式
RSC 帶來了一些新的資料取得思維。以前我們用 useEffect + fetch 形成瀑布式請求,現在可以直接在 Server Component 裡面平行取資料:
// app/dashboard/page.tsx
import { Suspense } from 'react';
async function UserStats() {
const stats = await fetchUserStats();
return <StatsCard data={stats} />;
}
async function RecentActivity() {
const activities = await fetchRecentActivity();
return <ActivityList items={activities} />;
}
export default function DashboardPage() {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<p>載入統計中...</p>}>
<UserStats />
</Suspense>
<Suspense fallback={<p>載入活動中...</p>}>
<RecentActivity />
</Suspense>
</div>
);
}
搭配 Suspense,每個區塊可以獨立載入,不用等全部資料都回來才顯示畫面。使用者體驗好很多。如果你想進一步優化表單處理,可以搭配 Next.js Server Actions 教學 來實現更流暢的資料互動。
初學者常見的 RSC 錯誤
我自己踩過不少坑,這邊整理幾個最常見的錯誤:
1. 在 Server Component 裡用 useState 或 useEffect
Server Component 沒有狀態、沒有生命週期。如果你看到 "useState is not a function" 之類的錯誤,八成是忘了加 'use client'。
2. 把整個頁面都標成 'use client'
這樣等於放棄了 RSC 的所有好處。正確做法是只把需要互動的最小元件標成 Client Component,其他盡量留在 Server 端。
3. 在 Client Component 裡 import Server Component
這是不行的。資料流向只能是 Server → Client,不能反過來。如果你需要在 Client Component 裡嵌入 Server Component 的內容,得透過 children prop 傳入:
// ✅ 正確做法:透過 children 傳入
// app/layout.tsx (Server Component)
import { ClientWrapper } from './ClientWrapper';
import { ServerContent } from './ServerContent';
export default function Layout() {
return (
<ClientWrapper>
<ServerContent />
</ClientWrapper>
);
}
// ClientWrapper.tsx
'use client';
export function ClientWrapper({ children }) {
const [isOpen, setIsOpen] = useState(true);
return isOpen ? <div>{children}</div> : null;
}
4. 傳遞不可序列化的 props
從 Server Component 傳到 Client Component 的 props 必須是可以 JSON 序列化的。函數、Date 物件、Map、Set 這些都不行,要特別注意。
RSC 帶來的效能提升
說了這麼多概念,RSC 到底能帶來多少實質的效能改善?根據我在幾個專案中的實測數據:
- JavaScript bundle size 減少 30-50%:大型套件(如 markdown 解析、syntax highlighting)不再進入 client bundle
- 首次載入時間(LCP)改善 20-40%:更少的 JS 意味著更快的解析和執行
- Time to Interactive 顯著降低:因為 hydration 的範圍縮小了
- 伺服器端資料存取延遲幾乎為零:元件直接在靠近資料庫的地方運行
這些數字在大型應用中會更明顯。如果你的專案有大量的內容展示頁面(像是部落格、電商商品頁),RSC 的效果會非常好。
RSC 最佳實踐總整理
最後,讓我把這篇 React Server Components 教學的重點整理成幾條實用的原則:
- 預設用 Server Component,只有需要互動時才改成 Client Component
- 把 'use client' 的邊界盡量往葉節點推——越靠近互動發生的地方越好
- 善用 Suspense 做串流渲染,讓使用者更快看到內容
- 資料取得放在 Server Component,透過 props 傳給 Client Component
- 注意 props 的可序列化性,Server → Client 的邊界不能傳函數
- 善用 React 的 cache 函數避免重複的資料請求
React Server Components 代表了 React 框架發展的一個重要方向。隨著 React 19 新功能教學 中提到的各種新 API 陸續穩定,RSC 的開發體驗只會越來越好。如果你現在還沒開始學,真的建議花點時間跟上——這不是一個可能會過時的實驗性功能,而是 React 的未來。
有任何問題歡迎在下方留言討論,我會盡量回覆大家。Happy coding!
繼續閱讀
React Signals 是什麼?2026 年最值得關注的前端狀態管理新方案完整解析
相關文章
你可能也喜歡
探索其他領域的精選好文