React Server Components と Client Components の仕組み
Published on 2026年1月2日
React0. はじめに
過去にレンダリングに関する詳細をふかぼったことがありました。 深ぼった内容を改めて言語化するためにこの記事を作成しております。
概要
React Server Components(RSC)とClient Componentsは、サーバーとクライアントで役割を分担することで、パフォーマンスとユーザー体験を向上させる仕組みである。
1. サーバーコンポーネントとクライアントコンポーネントの基本
サーバーコンポーネント(Server Components)
サーバー側でのみ実行され、HTMLとして事前にレンダリングされる。
特徴
- データベースやファイルシステムに直接アクセス可能
- JavaScriptバンドルに含まれない(クライアントに送信されない)
useStateやuseEffectなどのフックは使えない- イベントハンドラ(
onClickなど)は使えない
クライアントコンポーネント(Client Components)
ブラウザで実行され、インタラクティブな機能を担当する。
特徴
"use client"ディレクティブで宣言- 状態管理やイベント処理が可能
- ブラウザAPIにアクセス可能
2. SSR(Server Side Rendering)のレンダリングの流れ
1. クライアントがリクエスト
↓
2. サーバーでReact Server Componentsをレンダリング
↓
3. RSC Payload(シリアライズされたツリー)を生成
↓
4. クライアントコンポーネントの参照を含めてHTMLを送信
↓
5. ブラウザでHTMLを即座に表示(この時点ではまだ動かない)
↓
6. JavaScriptバンドルをダウンロード
↓
7. ハイドレーション実行(HTMLにJSの機能を接続)
↓
8. インタラクティブなページが完成
3. コンポーネントの境界
ServerComponent(サーバーで実行)
│
┌─────────┴─────────┐
│ │
ServerComponent ClientComponent ← "use client"
│ │
│ ┌────┴────┐
ServerComponent │ │
Client内の Client内の
子コンポ 子コンポ
重要なポイント
- サーバーコンポーネントはクライアントコンポーネントをインポートできる
- クライアントコンポーネントはサーバーコンポーネントを直接インポートできない(childrenとして渡すことは可能)
4. ハイドレーション(Hydration)とは
定義
サーバーで生成された静的なHTMLに、クライアント側でJavaScriptの機能を「注入」して、インタラクティブにする処理。
「水分を与える(hydrate)」という意味で、乾いた静的HTMLに「命を吹き込む」イメージ。
具体的な流れ
1. サーバーがHTMLを生成して送信
┌─────────────────────────────┐
│ <button>クリック: 0</button> │ ← ただのHTML文字列
└─────────────────────────────┘
↓
2. ブラウザがHTMLを表示(この時点ではボタンを押しても何も起きない)
↓
3. JavaScriptがロードされる
↓
4. ハイドレーション実行
- ReactがDOMを走査
- イベントリスナーを登録(onClick など)
- 状態管理を接続(useState など)
↓
5. インタラクティブになる(ボタンが動くようになる)
ハイドレーションの注意点
サーバーで生成したHTMLとクライアントでReactが期待する構造が一致している必要がある。一致しないと「ハイドレーションエラー」が発生する。
// ❌ エラーになる例
function BadComponent() {
return <p>{Math.random()}</p>; // サーバーとクライアントで値が違う
}
// ❌ エラーになる例
function BadComponent() {
return <p>{new Date().toLocaleString()}</p>; // 時間がずれる
}
5. SSRにおけるJSの扱い
サーバーから送られるもの
SSRでは、JS自体はサーバーサイドでレンダリングした時点では含まれない。レンダリングしたHTMLをクライアントサイドで展開し、その後JSを付与する形になる。
<!-- サーバーから届くもの -->
<!DOCTYPE html>
<html>
<body>
<!-- レンダリング済みのHTML(すぐ見える) -->
<div id="root">
<article>
<h1>タイトル</h1>
<p>本文...</p>
<button>いいね: 0</button>
<!-- まだ動かない -->
</article>
</div>
<!-- JSは別途読み込まれる -->
<script src="/static/js/bundle.js" defer></script>
</body>
</html>
時間軸での動き
[HTML到着] [JS到着] [ハイドレーション完了]
↓ ↓ ↓
画面表示 まだ動かない ボタンが動くように
6. バンドルとは
定義
複数のJavaScriptファイルを1つ(または少数)にまとめたもの。
なぜまとめるのか
【まとめない場合】
ブラウザ: Header.js ください
サーバー: はい
ブラウザ: Button.js ください
サーバー: はい
...(何百回も繰り返し)
→ リクエストが多すぎて遅い
【まとめた場合】
ブラウザ: bundle.js ください
サーバー: はい(全部入り)
→ 1回で済む
開発時とビルド後の比較
開発時のファイル構成
─────────────────────────────────────
src/
├── components/
│ ├── Header.jsx
│ ├── Button.jsx
│ └── Modal.jsx
├── utils/
│ └── helpers.js
└── App.jsx
+ node_modules/(何百、何千ものファイル)
ビルド後(バンドル後)
─────────────────────────────────────
dist/
├── index.html
└── bundle.js ← 全部まとめて1ファイルに
7. CSR(Client Side Rendering)の動き
CSRの流れ
【サーバーから届くもの】
<!DOCTYPE html>
<html>
<body>
<div id="root"></div> ← 空っぽ!何も表示されない
<script src="/static/js/bundle.js"></script> ← 全部入り
</body>
</html>
時間軸
[HTML到着] [JS到着] [レンダリング完了]
↓ ↓ ↓
真っ白 まだ真っ白 やっと見える&動く
←───── ここが長い ─────→
CSRのバンドルサイズ
CSRでは全てのコードがバンドルに含まれる。
bundle.js(例: 2MB)
├── React ランタイム
├── 全ページのコンポーネント
├── 全ての依存ライブラリ
├── ルーティング
└── 全てのロジック
→ 全部ダウンロードするまで何も見えない
なぜCSRだと全部バンドルに入るのか
CSRではブラウザが全てをやる必要がある
- ルーティング → どのページを表示するか判断
- データ取得 → APIを叩く
- レンダリング → DOMを構築
- インタラクション → イベント処理
これらに必要なコードが全部必要なため、バンドルが大きくなる。
8. SSR + RSC vs CSR の比較
レンダリングの違い
【SSR + ハイドレーション】
サーバー: ReactでHTMLを生成 → 送信
クライアント: HTMLを表示 → JSロード → ハイドレーション
HTML到着 JS到着 ハイドレーション
↓ ↓ ↓
[見える] [まだ動かない] [動く]
【CSR】
サーバー: 空のHTML → 送信
クライアント: JSロード → ReactがDOMを構築 → 表示
HTML到着 JS到着 レンダリング
↓ ↓ ↓
[真っ白] [真っ白] [見える&動く]
バンドルサイズの違い
【CSR】全部入りバンドル
─────────────────────────────────────
bundle.js
├── React ランタイム
├── 全ページのコンポーネント
├── 全ての依存ライブラリ
└── 全てのロジック
→ 全部ダウンロードするまで何も見えない
【SSR + RSC】分割されたバンドル
─────────────────────────────────────
サーバーで実行(クライアントに送らない)
├── サーバーコンポーネント
├── サーバー専用ライブラリ
└── データ取得ロジック
クライアントに送る
├── React ランタイム(軽量版)
├── クライアントコンポーネントのみ
└── 必要な依存ライブラリのみ
→ 小さいのでロードが速い
具体例での比較
// ブログページの例
function BlogPage() {
return (
<div>
<Header /> // 50KB
<Article /> // 100KB(マークダウンパーサー含む)
<Comments /> // 30KB
<LikeButton /> // 5KB(インタラクティブ)
<ShareButton /> // 5KB(インタラクティブ)
<Footer /> // 20KB
</div>
)
}
// 合計: 210KB
【CSR】
クライアントに送るJS: 210KB 全部
【RSC】
クライアントに送るJS: 10KB(LikeButton + ShareButton のみ)
残り200KBはサーバーで実行 → HTMLだけ送る
9. まとめ
| 項目 | CSR | SSR + RSC |
|---|---|---|
| 初期表示 | JSロード後 | HTML到着時 |
| バンドルサイズ | 大(全部入り) | 小(必要分のみ) |
| サーバー負荷 | 低(HTMLだけ) | 高(レンダリング) |
| SEO | 難しい | 良好 |
改善点の整理
改善点1表示速度
特定のURLにアクセスしたらすぐにHTMLが表示される状態にすることで、ユーザー体験が改善された。これはリクエスト後の表示速度を上げる改善である。
改善点2ダウンロード量
クライアント側でのJSファイルのダウンロード量が減ることで、さらに高速化が実現できている。
10. ベストプラクティス
"use client" は必要最小限に。インタラクティブな部分だけをクライアントコンポーネントにすることで、バンドルサイズを小さく保つ。
// ✅ 良い例インタラクティブな部分だけクライアント
function Page() {
return (
<article>
<h1>タイトル</h1> {/* サーバー */}
<p>長い本文...</p> {/* サーバー */}
<LikeButton /> {/* クライアント(小さい) */}
</article>
);
}
// ❌ 悪い例ページ全体をクライアントに
('use client');
function Page() {
return (
<article>
<h1>タイトル</h1>
<p>長い本文...</p>
<LikeButton />
</article>
);
}
// → 全部のJSがバンドルに含まれてしまう
11. レンダリング手法の使い分け(SSG / ISR / SSR / CSR)
各レンダリング手法の概要
レンダリング手法は「銀の弾丸」ではなく、要件に応じて適切な手法を選ぶ必要がある。
SSG(Static Site Generation)
ビルド時にHTMLを生成し、サーバーに配置する方式。
ビルド時
React コンポーネント → HTML ファイル生成 → サーバーに配置
リクエスト時
ユーザー → HTML をそのまま返す
→ サーバーでの処理が不要なので最速
適したケース
- データがリポジトリ内にある(Markdownファイルなど)
- ビルド時に全ページのHTMLを生成できる
- 更新時に再ビルド&再デプロイする運用が可能
例 個人ブログ、技術ドキュメント、LP、会社紹介ページ
ISR(Incremental Static Regeneration)
SSGをベースに、一定時間ごとにバックグラウンドで再生成する方式。
初回リクエスト
キャッシュされたHTMLを返す
バックグラウンド
一定時間経過後、次のリクエスト時に再生成をトリガー
例revalidate: 60(60秒ごとに再生成)
適したケース
- SSGに近い速度が欲しいが、ある程度リアルタイム性も必要
- 即時反映は不要だが、定期的にデータが更新される
例 ニュース記事一覧、商品カタログ
SSR(Server Side Rendering)
リクエストのたびにサーバーでレンダリングする方式。
リクエスト時
ユーザー → サーバーでデータ取得+レンダリング → HTML を返す
→ 毎回レンダリングするので SSG より遅いが、常に最新
適したケース
- リクエスト時にデータ取得が必要
- ユーザーごとに異なるデータを表示
- リアルタイムな反映が必須
例 ユーザーダッシュボード、検索結果、在庫状況
CSR(Client Side Rendering)
クライアント側で全てのレンダリングを行う方式。
リクエスト時
空のHTML → JSロード → クライアントでデータ取得&レンダリング
→ 初期表示は遅いが、クライアント操作が中心のアプリに向く
適したケース
- クライアントでの操作が中心
- SEOが不要
- 初期表示速度より操作性を重視
例 管理画面、エディタ、カート操作
選定の判断軸
| 判断軸 | SSG | ISR | SSR | CSR |
|---|---|---|---|---|
| データ更新頻度 | 低い | 中程度 | 高い | 高い |
| 即時反映の必要性 | 不要 | ある程度 | 必須 | 必須 |
| 初期表示速度 | 最速 | 速い | 速い | 遅い |
| サーバー負荷 | なし | 低い | 高い | なし |
| SEO | ◎ | ◎ | ◎ | △ |
実際のサービスでは混在する
ページの特性ごとに最適な手法を選ぶのが一番理想的な形です。 ですが、リソース的な問題や、エンジニアがどこまでレンダリングに対して知識があるのかにもよります。 そのため、データの更新が頻繁に起こるサービス・サイトにおいては基本的にSSRを用いたレンダリングに統一するのが有用かもしれません。
ECサイトの例
─────────────────────────────────────
/ (トップ) → SSG or ISR(更新少ない)
/about → SSG(静的)
/products → ISR(商品一覧、定期更新)
/products/[id] → SSR(在庫状況がリアルタイム)
/cart → CSR(ユーザー操作中心)
/mypage → SSR(ユーザー固有データ)
ブログにおける使い分け
「ブログ = SSG」と単純化できない。データの取得元と更新頻度で判断する。
【SSGで対応できるブログ】
─────────────────────────────────────
・記事データがリポジトリ内にある(Markdownファイルなど)
・ビルド時に全記事のHTMLを生成できる
・記事更新時に再ビルド&再デプロイする運用
例個人ブログ、技術ドキュメント
【SSRが必要なブログ】
─────────────────────────────────────
・記事データが外部API/CMSにある
・リアルタイムで記事が追加・更新される
・ビルドし直さずに反映したい
例ニュースサイト、企業ブログ(CMSで運用)
運用パターンの比較
パターン1SSG + 再ビルド
─────────────────────────────────────
CMS で記事更新 → Webhook でビルド発火 → 再デプロイ
メリット表示は最速
デメリット反映に数分かかる
パターン2ISR(Incremental Static Regeneration)
─────────────────────────────────────
SSG + 一定時間ごとにバックグラウンドで再生成
メリットSSGに近い速度 + ある程度リアルタイム
デメリット即時反映ではない
パターン3SSR
─────────────────────────────────────
毎リクエストでAPIからデータ取得 → レンダリング
メリット常に最新
デメリットSSGより遅い、サーバー負荷