React Server Components 実践:ToolsKu サイトの 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 のみ | コンポーネント内 async/await |
| SSG | ビルド → HTML | Client Component のみ | ビルド時取得 |
ToolsKu は SSG + RSC:Server Component をビルド時に描画し、Client Component は必要時に読み込みます。
Server と Client の境界
デフォルトはすべて 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 でできること:
- ファイルシステム・DB への直接アクセス
- async/await によるデータ取得
- サーバー専用モジュール(
fs、gray-matterなど)の import
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
}
ToolsKu の分担:
| 種別 | 例 | 理由 |
|---|---|---|
| Server | ブログ一覧、チュートリアル、ナビ | 静的コンテンツ、JS ゼロ |
| Client | PDF 結合、JSON 整形、動画変換 | File API と操作 |
| Server + Client | ツールページ layout (Server) + UI (Client) | JS 最小化 |
ToolsKu の RSC アーキテクチャ
ページ構造
src/app/[locale]/pdf/merge/page.tsx ← Server Component
├── generateMetadata() ← SEO(ビルド時)
├── ToolPageLayout ← Server(パンくず、タイトル)
└── PdfMergeClient.tsx ← Client('use client')
├── ファイルアップロード UI
├── pdf-lib 処理
└── ダウンロードボタン
コード分割の効果
従来 CSR ページ JS: ~180KB(React + ツール + UI)
RSC ページ JS: ~45KB(Client + ツールのみ)
削減: 約 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 にレンダリング—実行時のデータリクエストはゼロです。
性能測定
ToolsKu トップページ(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 境界を引き込む
// ❌ Server が Client 専用依存を import
import { someUtil } from './client-utils'; // client-utils に 'use client' がある場合
// ✅ 共有ロジックを server-safe モジュールへ
import { someUtil } from './shared-utils';
'use client' はモジュール単位—Client モジュールを import するとチェーン全体が client になります。
2. Server Component で Hooks
// ❌ コンパイルエラー
export default function Page() {
const [state, setState] = useState(0);
}
// ✅ Server + Client に分割
export default function Page() {
return <InteractivePart />; // 'use client'
}
3. 過剰な Client 化
// ❌ ページ全体が Client
'use client';
export default function ToolPage() {
return (
<div>
<h1>PDF 結合</h1> {/* 静的—client 不要 */}
<p>複数 PDF を結合</p>
<PdfMerger /> {/* これだけ client */}
</div>
);
}
// ✅ Client 境界を最小化
export default function ToolPage() {
return (
<div>
<h1>PDF 結合</h1>
<p>複数 PDF を結合</p>
<PdfMerger />
</div>
);
}
RSC と静的エクスポート
ToolsKu は output: "export" を使用。RSC はビルド時にレンダリングされます:
next build
→ generateStaticParams() を走査
→ 各 (locale, path) で Server Component 実行
→ 静的 HTML + Client Component JS chunks
→ CDN へデプロイ
静的エクスポートでは、RSC のサーバー負荷軽減は より小さい JS と高速な初回表示 に変換されます。
まとめ
React Server Components は Client Components の代替ではなく、JS が必要な境界の再定義です。ToolsKu のようにコンテンツ主・操作従のサイトでは、約 90% のページを JS ゼロで描画し、ツール操作部分だけ Client を読み込む—極限の初回表示性能のための重要な設計判断です。
ブラウザローカルツールを無料で試す →