先日、未経験入社3ヶ月の後輩から、こんな質問を受けました。
「React(CSR)だけで十分なのに、なぜわざわざサーバー側で動かすSSRが必要なんですか?」
実務でNext.jsを使っていると当たり前に感じてしまうことですが、いざ聞かれると言語化が難しいこのテーマ。私が考えている内容を、整理しました。
1. まず「料理の比喩」で整理する
ブラウザが画面を描画するまでの流れを、料理に例えて説明しました。
| CSR | SSR | |
|---|---|---|
| 例え | 材料を届けて、家で料理してもらう | 出来上がった料理をそのまま届ける |
| 初回表示 | 遅い | 速い(見た目は) |
| SEO | 弱い | 強い |
| サーバー負荷 | 低い | 高い |
※ この表だけ見るとSSR一択に見えますね笑
サーバー負荷とコストの問題があるため「使えるならどこでもSSR」にはなりません。
後ほど詳しく説明します。
CSR(Client-Side Rendering)
ブラウザがHTMLを受け取り、JavaScriptをダウンロードしてからReactが画面を描画する方式です。「材料(JS)を届けて、お客さんの家(ブラウザ)で料理してもらう」イメージです。最初に届くHTMLはほぼ空で、ReactがブラウザでDOMを作って画面を完成させます。
開発がシンプルで画面遷移も速い一方、初回表示が「白画面」になりがちです。これはReactのJavaScriptが読み込まれて実行されるまで、ブラウザが描画できるHTMLがほとんどないからです。ユーザーアクション中心の「管理画面」や「ダッシュボード」ならCSRで十分です。
シンプルな実装では useEffect + fetch を使うことが多いです。
useEffect(() => {
fetch('/api/stock') // Next.js API Routeを想定
.then(res => res.json())
.then(data => setStock(data));
}, []);
開発時のTips: CSRのAPI通信はブラウザから実行されるため、DevToolsのNetworkタブで通信の内容を確認できます。レスポンスの中身やタイミングを手軽に確認できるので、デバッグに役立ちます。
SSR(Server-Side Rendering)
サーバー側でReactを実行して完成済みのHTMLを生成し、ブラウザに返す方式です。「出来上がった料理をそのまま届ける」イメージで、届いた瞬間に画面の見た目が表示できます。
初回表示の「見た目」は速く、SEOやSNSプレビューに強い一方、サーバー負荷が増えるのがデメリットです。検索エンジンはJavaScriptを実行できますが、レンダリングには時間がかかることがあります。そのため最初からHTMLが完成しているSSRの方が、SEOやSNSプレビューでは有利になることが多いです。商品ページやブログなど、GoogleのクロールやSNSシェアが重要なページでは必須になります。
Pages Routerでは getServerSideProps を使います。
export async function getServerSideProps() {
const res = await fetch('https://api.example.com/product/1');
const product = await res.json();
return { props: { product } };
}
開発時のTips: SSRのAPI通信はサーバー側で実行されるため、ブラウザのNetworkタブには表示されません。「あれ、通信が見当たらない…」と詰まりやすいポイントです。確認したい場合はサーバーのログや、Next.jsを起動しているターミナルの出力を見る必要があります。
なお、Next.jsにはもう一つ「SSG(静的生成)」という方法もあります。「事前にHTMLを作っておく」方式で、後ほどECサイトの例で紹介します。
補足:ハイドレーションについて
SSRで「届いた瞬間に画面が表示できる」と書きましたが、厳密には「見た目は表示されるが、ボタンなどのJS操作ができるようになるまでに少しタイムラグがある」という点も知っておくと正確です。これをハイドレーションと言います。先ほどの比喩で言うと「料理は届いたけど、お箸(JS)が届くまで少し待つ」イメージです。
2. 「サーバー処理が重かったら、SSRでも遅くない?」
後輩から鋭いツッコミがありました。
「サーバー側でDBクエリなどの重い処理をしたら、結局HTMLが届くのが遅くなって、ユーザーは白画面を見るのでは?」
確かにその通りです。これに関連する指標としてTTFB(Time to First Byte)があります。ブラウザがリクエストを送ってから、最初のデータ(HTML)が届き始めるまでの時間のことです。
SSRはサーバーでデータ取得やレンダリングを行うため、このTTFBが長くなりやすいのが弱点です。
これに対し、私は実務での「使い分け」をこう答えました。
SEO不要・体感速度優先の場合: あえてCSRにし、先に「枠(ガワ)」だけ表示させて、データ取得中はスケルトン表示にする。
スケルトン表示とは? コンテンツが読み込まれる前に「グレーの枠」を先に表示する手法です。YouTubeやXで、動画や投稿の「影」がぼんやり表示される状態がこれです。白画面よりも「読み込んでいる感」が伝わり、ユーザーの離脱を防ぐ効果があります。
SEO必須・最新性が必要な場合: SSRにして、サーバーでデータを埋め込んでから返す。ただし、CDNやバックエンド側のキャッシュでTTFBの遅さを補う。
3. 実践編:ECサイトの商品ページをどう設計するか
ここでSSGも含めた3つの使い分けを説明しました。
設計のコツは「変わらないもの」と「動くもの」を切り分けることです。
- 商品名・説明文(変わらない): SSGでビルド時にHTMLを生成 → CDNから配信されるため爆速で表示
- 在庫状況(動く): CSRでブラウザから直接取得 → 常に最新を表示
なぜ在庫だけCSRにするかというと、ビジネス的な理由があります。商品名が「1分前の情報」でも大きな問題はありませんが、在庫が「あり」と表示されているのに実際は買えない、という状態はクレームに直結します。ユーザー体験への影響度(=間違えていると困る度合い)が高い情報ほど、リアルタイム性を確保するというのが判断の基準です。
// getStaticProps で商品情報を静的生成
export async function getStaticProps() {
const res = await fetch('https://api.example.com/product/1');
const product = await res.json();
return { props: { product } };
}
// 在庫だけはCSRで取得
useEffect(() => {
fetch('/api/stock/1') // Next.js API Routeを想定
.then(res => res.json())
.then(data => setStock(data));
}, []);
SSGの注意点と発展技(ISR)
SSGはデプロイ時にHTMLが固定されるため、商品名を変更したい場合は再ビルド&再デプロイが必要です。数万件の商品があり、価格や情報が頻繁に変わる規模のECサイトでは、ビルド時間が膨大になるためSSRを選ぶほうが合理的なケースもあります。私の実務でSSGをほとんど使ってこなかった主な理由もここにあります。
なお、Next.jsには**ISR(Incremental Static Regeneration)**という発展技もあります。「特定の時間ごとに静的ページを自動更新する」仕組みで、SSGの速さとSSRの新鮮さを両立できます。詳細は割愛しますが、「SSGは情報が固定される」という弱点をカバーする選択肢として頭に入れておくと良いでしょう。
4. 現場で迷わないための判断チャート
そのページにSEOは必要か?
│
├─ 不要 → CSR(管理画面・ダッシュボードなど)
│
└─ 必要
│
├─ リクエストごとに内容が変わる?(ユーザー別表示・リアルタイム性が必須)
│ └─ Yes → SSR
│
└─ No(情報が比較的安定している)
└─ SSG(+動くデータだけCSRで補完)
迷ったらまずCSRで作り、「SEOが必要になったとき」「初回表示の遅さが問題になったとき」にSSR/SSGへ移行するという順番で考えると、実務では判断しやすいです。
最後に:バックエンドとの連携も忘れずに
フロントのレンダリング戦略だけで設計は完結しません。バックエンド(Laravelなど)側のキャッシュ機能を活用してAPIの負荷を軽減することも、システム全体を速くするためには欠かせません。
「とりあえず動く」だけではなく、「そのページには何が必要で、どこで遅延が起きるか」を考えるのが設計の第一歩だと伝えて、今回のレクチャーを終えました。
補足:Next.js 13以降のApp Routerでは、ページ単位の関数(
getServerSideProps等)ではなく、Server ComponentとClient Componentを組み合わせる設計に変わりました。かつてのSSRやSSGといった機能は、現在はサーバーコンポーネント内でのデータ取得としてシームレスに表現されます。本記事はPages Routerベースですが、「どのデータをサーバーで扱うか」という本質的な考え方は同じですので、そのまま読み進めて問題ありません。
※内容誤っていればお気軽にコメントいただけますと幸いです。

コメント