Tsuzura

SSR・CSR・SSG・ISRの選び方

SSR・CSR・SSG・ISRの違いを、HTMLを作る時点、処理場所、利用者別データ、秘密情報、認可、キャッシュから整理し、画面・機能ごとの選び方を解説します。

SSR、CSR、SSG、ISRは、Webサイト全体で一つだけ選ぶ方式ではありません。画面や機能ごとに、HTMLを作る時点、コードとデータ取得を実行する場所、レスポンスを共有する範囲を分けて決めます。

この判断が必要なのは、表示速度だけが変わるからではありません。ブラウザへ届くデータ、認可を確認する場所、キャッシュへ保存される範囲、更新に失敗したときの状態も変わります。方式名だけで選ぶと、秘密情報の露出や、利用者別レスポンスの混在を見落とす可能性があります。

ここでいうレスポンスは、サーバーやAPIがブラウザへ返す内容です。認可は、利用者がそのデータや操作を使ってよいかを確認することです。秘密情報は、APIキーやデータベースの接続情報など、利用者へ公開しない値を指します。共有キャッシュは、CDNなどが複数の利用者へ同じレスポンスを再利用する仕組みです。キャッシュキーは、保存済みのレスポンスをどのリクエストへ再利用するかを区別する値です。

この記事は、初めてWebサービスの実装方式を選ぶ人を対象にしています。読み終えると、一つの画面または機能について、採用する方式、組み合わせる処理、未確認事項、見直し条件を「レンダリング判断票」へまとめられます。

入力として、一機能分の要件と受入条件と、必要な表示時間や更新頻度を決めた非機能要件が必要です。候補を同じ条件で比較する手順は技術選定の進め方で扱います。この記事では、フレームワーク別の設定方法、性能の数値比較、SEOの詳細、認証・認可の実装、CDN製品の比較は扱いません。

なぜレンダリング方式を決めるのか

レンダリングは、画面として表示するHTMLを作る処理です。レンダリング方式を決めるときは、HTMLの作成だけでなく、データ取得、ブラウザで動くJavaScript、キャッシュを一緒に確認します。

MDNのSSR解説は、サーバーがHTMLを生成してクライアントへ送る処理をServer-side renderingと説明しています。同じ解説では、SSRとClient-side renderingを一つのアプリケーション内で組み合わせられるとしています。

NuxtのRendering Modesでは、URLごとの経路で生成方式を分けられます。AstroのOn-demand renderingでも、ビルド時の生成とリクエスト時の生成を経路ごとに選べます。

したがって、最初に決めるのはサイト全体の方式名ではありません。対象を一つの画面または機能へ絞り、次の三つを分けます。

判断する軸 決めること 確認しないと起きる問題
HTMLを作る時点 ビルド時、リクエスト時、ブラウザでの実行時のどこか 更新時期や障害時の状態を説明できない
コードとデータ取得の実行場所 サーバー、ブラウザ、利用者に近い実行環境のどこか 秘密情報や利用できるAPIの境界を誤る
レスポンスを共有する範囲 全利用者、一人の利用者、共有しない、のどれか 別の利用者向けデータを返す可能性がある

方式名は、この三つを考えるための入口です。方式名だけでは、速度、最新性、安全性、検索結果への表示、JavaScriptが使えない場合の動作は決まりません。

選ぶ前に三つの軸を分ける

三つの軸を先に埋めると、SSR、CSR、SSG、ISRを同じ条件で比較できます。分からない欄は推測せず、「未確認」として担当者と確認期限を残します。

HTMLをいつ作るか

HTMLを作る主な時点は、ビルド時、リクエスト時、ブラウザでの実行時です。

  • ビルド時は、公開する成果物を作る処理の中でHTMLを生成します。
  • リクエスト時は、利用者からの要求を受けた後にサーバー側でHTMLを生成します。
  • ブラウザでの実行時は、配信されたJavaScriptをブラウザが実行してHTMLを生成します。

キャッシュに保存済みのHTMLを返す場合は、今回のリクエストで新しく生成していません。キャッシュを使うかは方式名だけでは分かりません。どの時点のデータからHTMLを作ったかも記録します。

コードとデータ取得をどこで実行するか

HTMLの生成場所と、すべてのコードの実行場所が同じとは限りません。サーバーで作ったHTMLへブラウザ上の処理を接続したり、ビルド時に作った画面からブラウザが最新データを取得したりできます。

実行場所ごとに、次を分けます。

  • 利用できる環境変数と秘密情報
  • データベースや外部APIへの接続
  • 利用者の権限を確認する処理
  • ブラウザへ送るHTML、JavaScript、データ
  • 実行環境で利用できるAPIとパッケージ

誰のレスポンスをどこまで共有するか

全利用者へ同じ内容を返すのか、ログイン状態、役割、組織、言語、地域などで内容が変わるのかを確認します。内容を変える入力は、キャッシュの可否とキャッシュキーを決める材料です。

利用者ごとに内容が変わるのに、共有範囲やキャッシュキーを説明できない場合は、方式を決めずに止めます。

SSR・CSR・SSG・ISRの違い

四つの方式は、主にHTMLを作る時点と、静的な生成物を更新する方法を表します。ここで示す「向く条件」は、本記事の判断整理です。採用条件は、使うフレームワークと実行基盤の公式資料で再確認してください。

SSR

SSRはServer-side renderingの略です。MDNでは、サーバーがHTMLを生成してクライアントへ送る方式と説明されています。

リクエスト時の利用者情報や、その時点のデータを使って初期HTMLを作る必要がある画面では候補になります。ただし、SSRという名称だけでは、毎回新しく生成するのか、キャッシュを使うのか、ブラウザでどれだけJavaScriptを動かすのかは分かりません。

利用者別のHTMLを共有キャッシュへ保存する可能性がある場合や、サーバーで行う認可を説明できない場合は採用を決めません。

CSR

CSRはClient-side renderingの略です。MDNのCSR解説では、JavaScriptをブラウザで実行してHTMLを生成する方式と説明されています。

ブラウザの機能を多く使い、JavaScriptが動く前に表示したい内容を初期HTMLへ含める必要がなく、秘密情報や最終的な認可判断をブラウザへ置かない画面では候補になります。ブラウザからサーバーAPIを呼ぶ場合は、そのAPI側で認可します。

JavaScriptの取得・実行やデータ取得に失敗したとき、必要な内容が表示できない構成になる場合があります。利用者が必要な情報を取得できるか、受入条件で確認します。

SSG

SSGはStatic Site Generationの略です。Next.jsのSSG公式資料では、HTMLをビルド時に生成し、リクエスト間で再利用できると説明しています。生成後にブラウザで最新データを取得する処理も併用できます。

全利用者へ同じ内容を返し、ビルド時に必要なデータがそろい、次の公開まで更新を待てる画面では候補になります。ビルド時のデータ取得に失敗した場合と、公開後に元データが変わった場合の扱いを決めます。

利用者別の秘密情報をビルド成果物へ含める必要がある場合や、更新の許容時間を説明できない場合は選びません。

ISR

ISRはIncremental Static Regenerationの略として使われます。ただし、Web標準の共通機能名ではありません。Next.jsのISR公式資料では、サイト全体を再ビルドせず静的コンテンツを更新し、期限後のリクエストへ既存版を返しながらバックグラウンドで再生成できます。Node.js実行環境や配備アダプターなどの条件もあります。

全利用者へ同じ内容を返し、公開の合間にも更新したい一方で、一定時間は古い内容を返してよい画面では候補になります。許容できる古さ、再生成の開始条件、失敗時に最後の正常版を返すか、誰が手動で無効化するかを先に決めます。

フレームワークや配備先によって、静的コンテンツを再生成する機能の名称と動作が異なります。NuxtのRendering Modesでも、経路ごとの再生成設定は配備先によって変わります。利用する版の公式資料で、再生成、キャッシュ、失敗時の動作を確認できない場合は保留します。

方式 HTMLを作る主な時点 主な候補条件 採用を止める条件
SSR リクエスト時のサーバー 利用者やリクエスト時点で初期HTMLが変わる 認可、共有キャッシュ、障害時を説明できない
CSR ブラウザでの実行時 ブラウザ中心の操作で、初期HTMLを要件にしない 秘密情報や最終認可をブラウザへ置く必要がある
SSG ビルド時 全利用者で同じ内容を、次の公開まで再利用できる 更新期限、ビルド失敗時、生成物へ含むデータが未確認
ISR ビルド時の生成物を運用中に再生成 全利用者で同じ内容を、許容した古さで更新できる 実装仕様、最大の古さ、無効化、再生成失敗時が未確認

同列に比較しない関連語

Hydration、Server Components、Streaming、Edgeは、四方式を置き換える別の候補ではありません。別の判断軸として、必要な場合だけ組み合わせます。

Hydration

Hydrationは、サーバーやビルド時に生成したHTMLへ、ブラウザ上の操作処理を接続することです。ReactのhydrateRoot資料は、既存HTMLへReactの処理を接続して操作可能にすると説明しています。

HTMLを作る時点とは別の判断です。サーバー側とブラウザ側の初期出力が一致しないと、再描画や操作の接続に問題が生じる可能性があります。

Server Components(RSC)

RSCはReact Server Componentsの略です。React Server Componentsの公式資料では、RSCはビルド時にもリクエスト時にも実行でき、初期HTMLをSSRするかは別に選べます。したがって、RSCとSSRは同義ではありません。

確認するのは、どのコンポーネントをどの環境で実行し、どのデータをクライアント側へ渡すかです。

Streaming

Streamingは、ページ全体の完成を待たず、準備できた部分からレスポンスを送る方法です。Astroの公式資料では、HTMLを分割して送れる一方、送信開始後はコンポーネントからヘッダーを変更できないと説明しています。

生成時点や実行場所とは別の判断です。

Edge実行

この記事では、Edgeをレンダリング方式ではなく、処理を利用者に近い実行環境へ置く配備上の軸として扱います。Nuxtの公式資料も、Edge-side renderingを実際のレンダリング方式より配備先に近い概念として説明しています。

Edgeを選ぶ場合は、Node.js APIやパッケージの互換性、データの保存場所、実行時間、配備アダプターを確認します。「Edgeだから速い」という理由だけでは決めません。

秘密情報・認可・キャッシュの境界

秘密情報、認可、共有キャッシュのどれかを説明できない場合は、レンダリング方式の採用を保留します。画面が表示できることと、安全に利用できることは別です。

ブラウザへ届く情報を確認する

サーバーで秘密情報を読んでも、HTML、クライアント向けのデータ、Props(コンポーネントへ渡す値)へ返せばブラウザへ届きます。Next.jsのData Securityガイドは、サーバーからクライアントへ渡すデータを、認可後の安全な最小データへ絞るよう示しています。

OWASPのSecrets Managementガイドは、秘密情報をソースやログへ含めず、必要な主体だけが扱えるようにする方針を示しています。ブラウザから確認できる次の出力へ、秘密情報が含まれないことを確認します。

  • 生成されたHTML
  • ブラウザへ配信するJavaScript
  • ページ用のデータや直列化されたProps
  • APIのレスポンス

エラー表示とログにも秘密情報を残しません。

環境変数名に公開用の接頭辞が付いていないことや、Server-only(サーバー専用)という名前だけでは、出力への混入を防いだ証拠になりません。

表示制御とサーバー側認可を分ける

ボタンや項目をブラウザで非表示にしても、その操作を実行できないとは限りません。OWASPのAuthorization Cheat Sheetは、リクエストごとに権限を確認し、クライアント側の制御を最終的な認可に使わないよう示しています。

ブラウザ側の表示制御は利用しやすさのために使えます。データ取得、更新、削除、外部送信を許可する判断は、サーバー、ゲートウェイ、サーバーレス関数など、利用者が変更できない境界で行います。一つの操作について、誰が、どの対象へ、何をしてよいかを決める手順は、認証と認可の判断表で扱います。

一人用と共有キャッシュを分ける

HTTP Cacheの仕様であるRFC 9111では、Shared Cacheは複数利用者へレスポンスを再利用し、Private Cacheは一人の利用者だけに使います。privateはShared Cacheへの保存を、no-storeはPrivate・Shared双方への保存を制限します。ただし、これらの指定だけで情報の非露出を保証するものではありません。

共有する前に、レスポンスが本当に全利用者で同じかを確認します。ログイン状態、役割、組織、言語、地域、Cookie、ヘッダー、URLクエリによって内容が変わる場合は、共有しないか、違いを安全に区別する必要があります。

キャッシュキー、Vary、再生成、無効化を分ける

RFC 9111では、HTTPのキャッシュキーは少なくともリクエストの方法と対象URLを使います。Varyは、保存済みレスポンスを選ぶときに区別するリクエストヘッダーを示します。実際のCDNやフレームワークが追加するキーと、Varyの扱いは別に確認します。

Authorizationヘッダーで認証情報を送るリクエストでは、レスポンスを共有キャッシュが再利用するために、共有保存を明示的に許可する指示が必要です。ここでいうAuthorizationは、本文で説明した認可の判断ではなく、HTTPのヘッダー名です。一方、Cookieを使う認証では、この規則が自動的に同じ形で適用されるとは限りません。認証方式と配備先の実際の設定を確認します。

フレームワークの再生成は、静的生成物をいつ作り直すかを扱います。レスポンスのCache-Controlは、キャッシュへの保存や再利用の条件を伝えるヘッダーです。Varyは保存済みレスポンスを選ぶ条件を補います。配備先がこれらを連動させる場合でも、同じ設定とは限りません。

CloudflareのWeb Cache Poisoning防止ガイドは、キャッシュキーに含まれない入力がレスポンスを変えると、同じキーを使う別のリクエストへ意図しないレスポンスが再利用される可能性を説明しています。共有キャッシュへ置く対象は、キーに含まれない入力で内容が変わらないかを確認します。

画面・機能ごとに選ぶ

次の表は、候補を絞るための本記事独自の判断整理です。該当する行を採用結果として使わず、公式資料と実際の配備設定で確認します。

条件 最初に検討する候補 追加で確認すること
全利用者で同じ内容で、ビルド時に確定し、次の公開まで更新を待てる SSG ビルド失敗時、元データの更新時、成果物へ含むデータ
全利用者で同じ内容だが、公開の合間にも更新したい ISRまたは静的生成と再検証の仕組み 許容する古さ、再生成失敗時、無効化方法、配備先の対応
ログイン状態、役割、組織、リクエスト時点で初期HTMLが変わる SSRまたはリクエスト時のサーバー処理 サーバー側認可、共有キャッシュの無効化または安全なキー、障害時
初期HTMLを要件にせず、ブラウザ中心の操作が必要 CSR JavaScript・API失敗時、秘密情報の不使用、API側認可
大部分は共通で、一部だけ操作が必要 SSGまたはSSRとHydrationの併用 Hydration範囲、初期出力一致、送るJavaScript量
大部分は共通で、一部だけ利用者別 共通部分とリクエスト時処理を分離 利用者別部分の認可、共有範囲、キャッシュ境界

更新頻度と利用者差で候補を絞る

最初に、内容を変える入力と更新期限を書き出します。全利用者で同じか、誰の状態で変わるか、最大でどれだけ古いデータを許容するかを決めます。

更新期限が決まらない場合は、ISRやキャッシュ期間を選べません。利用者差が分からない場合は、共有キャッシュを有効にしません。

初期表示と操作性を確認する

初期HTMLに必要な内容、JavaScriptが動く前に必要な内容、操作開始までに必要な処理を分けます。表示時間の合格値は、レンダリング方式の一般論ではなく非機能要件で決めます。

CSR、SSR、SSGという名称から速度を推測せず、対象の画面、データ量、実行環境で測定します。

配備先の制約と失敗時を確認する

フレームワークで選べる方式でも、配備先が同じ機能を提供するとは限りません。利用するフレームワークと版、実行環境、アダプター、CDN、通信を中継するプロキシ、複数の実行単位を記録します。

次の失敗時に何を返すかも決めます。

  • ビルド時のデータ取得に失敗した
  • リクエスト時のサーバー処理に失敗した
  • ブラウザのJavaScriptまたはAPI取得に失敗した
  • 再生成に失敗した
  • キャッシュの無効化が一部で失敗した
  • 新旧の公開版が同時に動いた

レンダリング判断票を作る

判断票は、一つの画面または機能ごとに作ります。フレームワーク全体の設定と、今回の対象に適用される設定を分けます。

最初に埋める項目

最初は次の六つだけを埋めます。

  1. 対象の画面または機能
  2. HTMLを作る時点
  3. コードとデータ取得を実行する場所
  4. ブラウザへ届くHTML、JavaScript、データ
  5. レスポンスを共有する範囲
  6. 採用候補と、未確認なら止める条件

ここまで決まらなければ、詳細なキャッシュ期間や実装タスクへ進みません。

レンダリング判断票

対象
- 画面・機能:
- 要件・受入条件の参照先:
- 非機能要件の参照先:
- フレームワーク・版:
- 実行環境・アダプター・配備先:

生成と実行
- HTMLを作る時点: [ビルド時 / リクエスト時 / ブラウザ実行時 / キャッシュから再利用 / 未確認]
- コードを実行する場所:
- データを取得する場所:
- 操作処理を接続する範囲(Hydration):
- 分割して送る範囲(Streaming):
- Edge実行: [使う / 使わない / 未確認]

ブラウザへ届くもの
- HTMLに含まれるデータ:
- JavaScriptに含まれるコード・設定:
- ページ用データ・Props・APIレスポンス:
- 秘密情報を使用する場所:
- 秘密情報が出力へ含まれない確認方法:

認可と利用者差
- レスポンスを変える入力:
- サーバー側で認可する場所:
- 全利用者で同じレスポンスか: [はい / いいえ / 未確認]
- 利用者別データの範囲:

キャッシュ
- キャッシュ層: [ブラウザ / フレームワーク / 実行単位 / CDN / その他]
- キャッシュ種別: [一人の利用者専用 / 複数利用者で共有 / 保存しない / 未確認]
- キャッシュキー:
- Varyと、レスポンスが変わる条件:
- 保存期間:
- 許容する古さ:
- 再検証・再生成の条件:
- 無効化の方法と担当者:
- 再生成・無効化に失敗した場合:

判断
- 候補方式と組合せ:
- 採用理由:
- 採用しなかった方式と理由:
- 確認した公式資料と確認日:
- 確認記録(生成HTML、配信JavaScript、通信結果、キャッシュ設定)の参照先:
- 未確認事項:
- 保留を解除する条件:
- 判断者:
- 見直す条件:

保留にする条件

次のどれかに当てはまる場合は方式を採用せず、必要な情報がそろうまで判断を保留します。

  • 秘密情報を読む場所と、ブラウザへ届く出力を説明できない
  • 認可を確認する信頼できる境界が決まっていない
  • 利用者別レスポンスを共有キャッシュへ保存する可能性がある
  • キャッシュキー、レスポンスが変わる条件、保存期間、無効化が分からない
  • ISRなどの名称は分かるが、利用する版と配備先での動作を確認できない
  • 生成、データ取得、再生成、無効化の失敗時を決めていない
  • 選んだ実行環境で必要なAPIやパッケージを使えるか確認できない

AIへ任せる範囲

AIには、リポジトリと公式資料から次の情報を根拠付きで整理させられます。

  • 画面ごとの現在のレンダリング設定
  • サーバーとブラウザの境界
  • ブラウザへ渡しているデータの候補
  • キャッシュ関連のヘッダー、キー、再検証、無効化設定
  • 利用するフレームワーク、実行環境、アダプターの版
  • 未確認項目と追加で読む公式資料

秘密情報の値はAIへ渡しません。認可の正しさ、共有キャッシュへ保存してよいか、残るリスクを受け入れるかは、人が実際の要件、契約、配備設定、検証結果を確認して決めます。AIが「安全」「問題なし」と回答したことを証拠にしません。

実装・テスト・公開へ渡す

判断票ができたら、実装をAIに渡す一件のタスクへ分けます。実装後はソフトウェアテストの種類と選び方で、ブラウザへ届くデータ、認可、利用者差、キャッシュ、無効化、失敗時を確認します。

AIが生成した変更は、AI生成コードをレビューするで実行場所とデータ境界を最終差分から再確認します。本番へ進めるかは本番公開前の確認で判断します。

判断票の完成条件は、方式名が一つ書かれていることではありません。生成時点、実行場所、ブラウザへ届くもの、認可、共有範囲、キャッシュ、失敗時、未確認事項、判断者、見直し条件を説明できることです。

出典