React Server Components 實戰:工具庫網站的 RSC 架構實踐

技术架构(更新於 2026年5月21日)

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 取得資料
  • 匯入僅在伺服器端可用的模組(如 fsgray-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——這是實現極致首屏效能的關鍵架構決策。

本站提供瀏覽器本地工具,免註冊即可試用 →

#React#Server Components#Next.js#SSR#性能