React Server Components 實戰:工具庫網站的 RSC 架構實踐
Server Components 解決了什麼問題?
傳統 React 應用的痛點:
瀏覽器下載 JS bundle → 解析執行 React → 發起 API 請求 → 渲染 UI
↑ 慢 ↑ 阻塞 ↑ 瀑布流
每個頁面都需要下載完整的 React 執行環境 + 元件程式碼 + 資料取得邏輯。對於內容為主的工具站,大量 JS 是不必要的。
React Server Components (RSC) 將元件渲染移到伺服器(或建置時),瀏覽器只接收 HTML + 最小化的 Client Component JS。
渲染模型對比
| 模式 | 渲染位置 | JS 傳送到瀏覽器 | 資料取得 |
|---|---|---|---|
| CSR | 瀏覽器 | 全部元件 JS | useEffect 瀑布流 |
| SSR | 伺服器 → HTML | 全部元件 JS(hydration) | getServerSideProps |
| RSC | 伺服器/建置時 | 僅 Client Component JS | 元件內直接 async/await |
| SSG | 建置時 → HTML | 僅 Client Component JS | 建置時取得 |
工具庫使用 SSG + RSC:建置時渲染所有 Server Component,Client Component 按需載入。
Server vs Client Component 邊界
預設規則:一切都是 Server Component
// ✅ Server Component(預設)— 無 'use client'
export default async function BlogPage({ params }) {
const posts = getAllPosts(params.locale, 'blog'); // 直接讀檔案系統
return (
<div>
<h1>{posts.length} 篇文章</h1>
{posts.map(p => <PostCard key={p.slug} post={p} />)}
</div>
);
}
Server Component 可以:
- 直接存取檔案系統、資料庫
- 使用 async/await 取得資料
- 匯入僅在伺服器端可用的模組(如
fs、gray-matter)
Server Component 不能:
- 使用 useState、useEffect 等 Hooks
- 綁定事件處理器(onClick 等)
- 存取瀏覽器 API(window、localStorage)
何時需要 Client Component
'use client'; // ← 標記為 Client Component
import { useState } from 'react';
export function PdfMergeClient() {
const [files, setFiles] = useState<File[]>([]);
// 需要互動、狀態、瀏覽器 API → Client Component
}
工具庫的劃分原則:
| 元件類型 | 範例 | 原因 |
|---|---|---|
| Server | 部落格列表、教學頁、導覽列 | 靜態內容,零 JS |
| Client | PDF 合併、JSON 格式化、影片轉碼 | 需要檔案 API 和互動 |
| Server + Client 組合 | 工具頁 layout (Server) + 工具 UI (Client) | 最小化 JS |
工具庫的 RSC 架構
頁面結構
src/app/[locale]/pdf/merge/page.tsx ← Server Component
├── generateMetadata() ← SEO 中繼資料(建置時)
├── ToolPageLayout ← Server Component(麵包屑、標題)
└── PdfMergeClient.tsx ← Client Component('use client')
├── 檔案上傳 UI
├── pdf-lib 處理邏輯
└── 下載按鈕
程式碼分割效果
傳統 CSR 頁面 JS: ~180KB (React + 工具邏輯 + UI 元件)
RSC 頁面 JS: ~45KB (僅 Client Component + 工具邏輯)
減少: ~75%
Server Component 渲染的 HTML(標題、描述、麵包屑、FAQ)零 JS 開銷。
資料取得:建置時 vs 執行時
// 部落格文章:建置時讀取 MDX 檔案
export default async function BlogPostPage({ params }) {
const { slug, locale } = await params;
const result = getRenderedPost(slug, locale, 'blog'); // 讀檔案系統
if (!result) notFound();
return <ContentPostArticle post={result.post} rendered={result.rendered} />;
}
// 工具頁:建置時載入 i18n 訊息
export default async function PdfMergePage({ params }) {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: 'tools.pdf_merge' });
return (
<ToolPageLayout title={t('title')} description={t('description')}>
<PdfMergeClient />
</ToolPageLayout>
);
}
所有資料在 next build 時取得並渲染為靜態 HTML——執行時零資料請求。
效能收益實測
工具庫首頁(zh-CN)的資源載入對比:
| 指標 | 純 CSR 估算 | RSC + SSG 實測 |
|---|---|---|
| 首屏 JS | ~180KB | ~12KB |
| FCP | ~1.2s | ~0.4s |
| LCP | ~2.1s | ~0.8s |
| TTI | ~2.8s | ~1.2s |
| HTML 大小 | ~5KB (空殼) | ~45KB (完整內容) |
HTML 變大但 JS 大幅減小——對於內容為主的工具站,這是正確的權衡。搜尋引擎和使用者都能立即看到完整內容。
常見陷阱
1. 意外引入 Client Component 邊界
// ❌ Server Component 匯入了 Client Component 的依賴
import { someUtil } from './client-utils'; // 如果 client-utils 有 'use client'
// ✅ 將共用邏輯提取到獨立的 server-safe 模組
import { someUtil } from './shared-utils';
'use client' 是模組級指令,匯入 Client Component 模組會將整個匯入鏈標記為 client。
2. 在 Server Component 中使用 Hooks
// ❌ 編譯錯誤
export default function Page() {
const [state, setState] = useState(0); // Hooks 只能在 Client Component 中使用
}
// ✅ 拆分為 Server + Client
export default function Page() {
return <InteractivePart />; // 'use client' 元件
}
3. 過度 Client 化
// ❌ 整個頁面都是 Client Component
'use client';
export default function ToolPage() {
return (
<div>
<h1>PDF 合併</h1> {/* 靜態標題,不需要 client */}
<p>合併多個 PDF 檔案</p> {/* 靜態描述,不需要 client */}
<PdfMerger /> {/* 只有這個需要 client */}
</div>
);
}
// ✅ 最小化 Client 邊界
export default function ToolPage() {
return (
<div>
<h1>PDF 合併</h1>
<p>合併多個 PDF 檔案</p>
<PdfMerger /> {/* 只有這個元件有 'use client' */}
</div>
);
}
RSC 與靜態匯出的結合
工具庫使用 output: "export" 靜態匯出,RSC 在建置時渲染:
next build
→ 遍歷 generateStaticParams()
→ 每個 (locale, path) 執行 Server Component
→ 輸出靜態 HTML + Client Component JS chunks
→ 部署到 CDN
這意味著 RSC 的執行時優勢(減少伺服器負載)在靜態匯出中轉化為更小的 JS bundle 和更快的首屏。
總結
React Server Components 不是替代 Client Components,而是重新定義了 JS 的必要邊界。對於工具庫這類內容為主、互動為輔的網站,RSC 讓 90% 的頁面內容零 JS 渲染,僅工具操作區域載入必要的 Client Component——這是實現極致首屏效能的關鍵架構決策。
本站提供瀏覽器本地工具,免註冊即可試用 →