後輩に聞かれて初めて言語化できた、Next.jsでCSRとSSRを使い分ける理由

スポンサーリンク

先日、未経験入社3ヶ月の後輩から、こんな質問を受けました。

「React(CSR)だけで十分なのに、なぜわざわざサーバー側で動かすSSRが必要なんですか?」

実務でNext.jsを使っていると当たり前に感じてしまうことですが、いざ聞かれると言語化が難しいこのテーマ。私が考えている内容を、整理しました。


スポンサーリンク

1. まず「料理の比喩」で整理する

ブラウザが画面を描画するまでの流れを、料理に例えて説明しました。

CSRSSR
例え材料を届けて、家で料理してもらう出来上がった料理をそのまま届ける
初回表示遅い速い(見た目は)
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ベースですが、「どのデータをサーバーで扱うか」という本質的な考え方は同じですので、そのまま読み進めて問題ありません。
※内容誤っていればお気軽にコメントいただけますと幸いです。

コメント

タイトルとURLをコピーしました