0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SSGとSSRの違いとそれぞれの活用方法

0
Posted at

はじめに

技術ブログと、ログインしたユーザーの注文履歴画面は、どちらも Web ページですが、必要なデータの性質は異なります。ブログ記事は複数の訪問者が同じ内容を読めればよい一方、注文履歴はユーザーの認証状態やリクエスト時点の情報によって変わります。

この違いを考えると、SSG と SSR を選ぶ基準が明確になります。重要なのは、「HTML をいつ生成するか」「生成した結果を誰と、どれくらいの期間共有できるか」という問いです。この記事では、両者の基本動作、活用例、キャッシュとデータ更新の関係を説明します。

レンダリングの考え方はさまざまなフレームワークに当てはまりますが、コード例は Next.js Pages Router と TypeScript を前提とします。App Router の設定や API とは混在させません。説明用の図は、初回のページリクエストの基本的な流れを簡略化したものです。図の中の文言は英語で表記しています。

1. 最大の違いは HTML を生成するタイミング

SSG(Static Site Generation、静的サイト生成)は、基本的にビルド時にページの HTML をあらかじめ生成する方式です。訪問者からリクエストが来たら、準備済みの結果を返し、複数のリクエストで同じ結果を再利用できます。

SSR(Server-Side Rendering、サーバーサイドレンダリング)は、リクエスト時にサーバーでページをレンダリングする方式です。リクエストに含まれる情報と必要なデータを使って HTML を生成できます。この記事では、ビルド時の生成と区別するために、SSR をリクエスト時のレンダリングという意味で使います。

SSG and SSR compared by HTML generation time

図1. SSG はリクエスト前に HTML を準備し、SSR はリクエストの処理中に HTML を生成します。共有レスポンスキャッシュを使わない基本的な SSR の流れと比較しています。

どちらの方式も、主要な内容を含む HTML をブラウザーに渡せます。違いは単にサーバーを使うかどうかではなく、レンダリングのコストをいつ負担し、その結果をどのように再利用するかにあります。

比較項目 基本的な SSG リクエスト時の SSR
HTML の生成時点 主にビルド時 リクエストの処理時
訪問時の主な処理 生成済みの結果を配信 必要なデータ処理とレンダリング
結果の再利用 同じ静的な結果を複数のリクエストで再利用 レスポンスのキャッシュ方針によって異なる
データの更新 再ビルド、または別途再生成の方針が必要 リクエスト時に参照するデータとキャッシュ方針で決まる
リクエスト固有の情報の反映 静的な結果だけでは制限がある Cookie やヘッダーなどの情報を利用可能
主に検討する場面 共有可能な公開コンテンツ リクエスト固有の情報を初期 HTML に含めたい画面

2. SSG は共有できるコンテンツを事前に準備する

ブログ記事やドキュメントサイトのように、訪問者ごとの内容が同じで、コンテンツの変更とデプロイのプロセスを結び付けられる場合は、SSG を検討しやすくなります。CMS から記事を取得する場合でも、ビルド時に取得すれば静的なページを生成できます。

基本的な処理の流れは次のとおりです。

  1. ビルド時に、ファイル、CMS、API などから必要なデータを読み込みます。
  2. データを使って HTML と必要な静的ファイルを生成します。
  3. 静的ホスティングや CDN から、生成済みの結果を訪問者へ配信します。

SSG flow from content source through build artifacts to CDN and visitors

図2. コンテンツの取得とレンダリングをビルド時に行い、訪問時には準備済みの結果を再利用します。

リクエストごとにページをレンダリングし直す必要がないため、同じコンテンツにアクセスが集中する場合も、配信経路をシンプルにできます。ただし、ページ数やビルド時に読み込むデータが増えるとビルド時間が長くなる可能性があり、コンテンツを変更した後にいつ再生成するかも決める必要があります。

たとえば、会社紹介や利用ガイドの変更をデプロイと同時に反映するなら、基本的な SSG が適している場合があります。記事が頻繁に更新される大規模サイトでは、すべてのページを毎回作り直すコストも検討します。

「静的」という言葉は、ボタン操作や検索、フィルターを使えないという意味ではありません。あらかじめ生成した HTML 上でも、ブラウザーの JavaScript でインタラクションを実装したり、追加データを取得したりできます。

3. SSR はリクエストに応じた内容を初期レスポンスに含める

ログイン状態、権限、リクエストパラメーターなど、アクセス時に分かる情報が初期画面に必要なら、SSR が選択肢になります。たとえば、認証済みユーザーのアカウント概要をサーバーで構成したり、リクエストの条件に合う結果を初期 HTML に含めたりできます。

共有 HTML レスポンスキャッシュを使わない場合の基本的な流れは、次のとおりです。

  1. サーバーがリクエストを受け取り、Cookie、ヘッダー、URL などの情報を確認します。
  2. 必要なデータを取得し、表示が許可された内容でページをレンダリングします。
  3. 生成した HTML をブラウザーに返します。

SSR request context data fetching rendering and HTML response

図3. SSR では、リクエストの処理経路にデータ処理と HTML 生成が含まれます。データ取得そのものにはキャッシュを使う場合もあります。

リクエストごとに必要な処理は、レスポンス時間とサーバーコストに影響します。特に、遅い外部 API やデータベースへの問い合わせが初期画面に不可欠なら、その遅延も考慮する必要があります。一方、適切なデータキャッシュやレスポンスキャッシュを使えば、重複する処理を減らせます。

そのため、SSR だからといって、必ずデータベースの最新の値を読み込むわけではありません。データソース側の更新遅延やキャッシュがあると、以前の値が表示されることがあります。また、一度開いたページが自動的に更新され続けるわけでもありません。継続的に更新するには、再リクエスト、ポーリング、リアルタイム通信など、別の戦略が必要です。

4. パフォーマンス・SEO・キャッシュを個別に確認する

SSG が常に速く、SSR が常に遅いとは限らない

SSG は訪問前に HTML の生成を終えるため、初期レスポンスを速く返しやすい構造です。ただし、実際の体感速度は CDN の位置、ネットワーク、画像サイズ、JavaScript の量と実行コストにも左右されます。SSR でも、データ処理やキャッシュを適切に設計すれば、十分に速いレスポンスを提供できます。

初期レスポンスを比較するときは TTFB(Time to First Byte、レスポンスの最初のバイトが届くまでの時間)を確認し、画面に見える最大のコンテンツ要素が表示されるタイミングは LCP(Largest Contentful Paint)などで別に確認します。HTML を速く受け取れたという事実だけで、画面全体が速く完成すると判断しないようにします。

どちらも主要な内容を HTML に含められる

SSG と SSR は、どちらも初期 HTML に主要なコンテンツを含められます。方式を選んだだけで検索順位が保証されるわけではありません。タイトル、メタデータ、アクセス可能なコンテンツ、レスポンスのステータスコード、サイト構造なども管理する必要があります。

React ベースの画面では、受け取った HTML にイベントやコンポーネントのロジックを接続する ハイドレーション(hydration)が続く場合があります。これは、HTML を生成するタイミングとは別の処理です。サーバーで計算した値を props として渡すなどして、初期 HTML とブラウザー側の初回レンダリングの結果に不一致が生じないようにします。

データキャッシュと HTML レスポンスキャッシュを区別する

API から取得した結果を再利用することと、完成した HTML を複数の訪問者に再利用することは、別の判断です。公開コンテンツでは共有レスポンスキャッシュを検討できますが、個人向けの内容は別のユーザーに渡らないように、キャッシュの範囲とキーを設計する必要があります。

レンダリング関数がサーバーで実行されるからといって、返したデータまでサーバーにだけ残るわけではありません。ページに渡す props はブラウザーから確認できるため、トークンなど、ブラウザーに公開してはいけない情報を含めないようにします。

5. Next.js の例で生成時点を比較する

次の例では、外部 API を使わずに、HTML で使用する時刻をいつ計算するかを比較します。Next.js と TypeScript が設定済みのプロジェクトで、ファイルをそれぞれ pages/ssg.tsx、pages/ssr.tsx に配置します。src/pages 構成のプロジェクトでは、そちらに配置してください。

SSG: getStaticProps

pages/ssg.tsx
import type { GetStaticProps } from "next";

type Props = { generatedAt: string };

export const getStaticProps: GetStaticProps<Props> = async () => {
  return {
    props: { generatedAt: new Date().toISOString() },
  };
};

export default function SsgPage({ generatedAt }: Props) {
  return (
    <main>
      <h1>SSG の例</h1>
      <p>データ生成時刻: {generatedAt}</p>
    </main>
  );
}

この例の getStaticProps は本番用のビルド時に実行され、返した時刻が静的な結果に含まれます。再ビルドや再生成を行わなければ、訪問者がページを再読み込みしても同じ値が表示されます。実際のサービスでは、この箇所で公開記事などのデータを取得できます。

SSR: getServerSideProps

pages/ssr.tsx
import type { GetServerSideProps } from "next";

type Props = { generatedAt: string };

export const getServerSideProps: GetServerSideProps<Props> = async ({ res }) => {
  // この例では生成時点を比較しやすくするため、レスポンスをキャッシュしません。
  res.setHeader("Cache-Control", "private, no-store");

  return {
    props: { generatedAt: new Date().toISOString() },
  };
};

export default function SsrPage({ generatedAt }: Props) {
  return (
    <main>
      <h1>SSR の例</h1>
      <p>データ生成時刻: {generatedAt}</p>
    </main>
  );
}

サーバーに新しいリクエストが届くと、時刻を再計算します。この例の no-store は動作を比較するための選択であり、すべての SSR ページに同じ方針を適用する必要があるという意味ではありません。実際に個人情報を取得する場合は、認証や権限確認も別途実装する必要があります。

開発モードではなく本番モードで確認する

next dev では getStaticProps もリクエストごとに実行されるため、開発画面だけで SSG の本番時の動作を判断すると混乱しやすくなります。標準的な build と start スクリプトがあるプロジェクトでは、次の順序で確認できます。

npm run build
npm run start

本番モードのサーバーで /ssg と /ssr をそれぞれ開き、再読み込みして表示時刻を比較します。サーバーログとレスポンスのキャッシュヘッダーも併せて確認すると、値の生成時点とキャッシュの影響を区別しやすくなります。

getStaticProps と getServerSideProps は Pages Router の API です。App Router ではこれらの API を使わず、サーバーコンポーネントとキャッシュ・再検証のモデルを基準に設計します。静的または動的に処理される範囲やデフォルトの動作は、バージョンや Cache Components などの設定によって異なるため、利用するプロジェクトに対応した公式ドキュメントを確認してください。サーバーコンポーネントであるという理由だけで、リクエストごとにレンダリングされると決め付けないことが大切です。

6. ISR で静的ページの更新戦略を補う

SSG の結果を再利用しながらコンテンツも更新したい場合に検討できるのが、ISR(Incremental Static Regeneration、増分静的再生成)です。サイト全体を再ビルドせずに、静的ページを再生成できます。

先ほどの SSG の例で getStaticProps を次の関数に置き換えると、Pages Router の時間ベースの再検証を確認できます。Props 型、import、ページコンポーネントは元の例を使います。

export const getStaticProps: GetStaticProps<Props> = async () => {
  return {
    props: { generatedAt: new Date().toISOString() },
    revalidate: 60,
  };
};

この設定は、正確に 60 秒ごとにページを自動更新するスケジュール処理ではありません。基本的な動作は次のとおりです。

  1. ページが生成され、結果がキャッシュに保存されます。
  2. 再検証の基準時間が経過するまでは、既存の結果を再利用します。
  3. 基準時間の経過後にリクエストが来ると、既存の結果を先に返し、バックグラウンドで再生成を開始します。
  4. 再生成に成功すると、以降のリクエストでは新しい結果を使います。

ISR timeline with cached response background regeneration and later updated response

図4. 60 秒は再検証を開始できる基準であり、すべての訪問者に 60 秒以内で新しい内容が表示されることを保証するものではありません。

基準時間が経過した後もリクエストがなければ、この時間設定だけで再生成は始まりません。再生成中は以前の結果が返される場合があり、失敗した場合は最後に成功した結果を維持します。CMS の変更イベントに合わせて再検証する方法もあるため、コンテンツの特性に応じて時間ベースかイベントベースかを検討します。

ISR を使うには、デプロイ環境のランタイムとキャッシュのサポートを確認する必要があります。この記事で扱う Pages Router の ISR は、静的ファイルだけを出力する Static Export ではサポートされません。

7. ページや領域の要件に合わせて組み合わせる

実務では、すべての画面を一つの方式に統一する必要はありません。次の表は、設計を判断するための活用例です。

画面・領域 最初に検討する方式 確認する条件
技術ブログ・ドキュメント・会社紹介 SSG 変更時の再ビルドとデプロイを許容できるか
複数のユーザーで共有するコンテンツ一覧 SSG + ISR どの程度の更新遅延を許容できるか
ログイン済みユーザーのアカウント概要 SSR または認証後のクライアント取得 個人向けの内容を初期 HTML に含める必要があるか
条件が多様な検索結果 SSR またはクライアント取得 初期レスポンス、検索への露出、条件数、キャッシュの可否
商品説明 + 在庫・ユーザー別機能 静的コンテンツと動的データの組み合わせ 領域ごとに鮮度や個人化の要件が異なるか

Rendering strategy examples for public personalized and mixed pages

図5. 公開コンテンツ、リクエスト固有のコンテンツ、異なる性質が混在する画面を区別して戦略を選びます。

たとえば、商品説明は SSG や ISR で配信し、在庫やログインユーザーのお気に入り状態は別途取得できます。ただし、画面に表示されている在庫や価格が決済時点でも同じである保証はないため、購入処理を行うサーバーで最終的な条件を再検証する必要があります。

個人向けの領域をブラウザーで読み込む場合は、ローディング状態、エラー状態、画面遷移も設計します。初期 HTML に必要な内容まで遅れて取得していないか、データリクエストが連鎖していないかも確認します。一つのページ内で静的・動的な領域を組み合わせる具体的な実装は、フレームワークとルーターによって異なります。

8. 選ぶ前に確認すること

  • 主要なコンテンツは、すべての訪問者に対して同じか。
  • ログイン状態・権限・リクエスト条件を初期 HTML に反映する必要があるか。
  • どの程度のデータ更新遅延を許容できるか。
  • データ取得と HTML レスポンス、それぞれのキャッシュ方針が決まっているか。
  • コンテンツ変更後の再ビルドや再検証を、どのように開始するか。
  • ビルド時間、サーバーコスト、外部 API の遅延や障害を検討したか。
  • デプロイ環境が、必要なレンダリングや再検証の機能をサポートしているか。
  • 本番モードで、レスポンス時間と実際の表示結果を確認したか。

おわりに

SSG は、複数のリクエストで共有できる結果を事前に準備する点に強みがあり、SSR はリクエスト時点の情報を初期レスポンスに反映するのに適しています。ISR やクライアント側のデータ取得を組み合わせれば、一つの方式だけでは対応しにくい要件を分担できます。

方式を選ぶときは、コンテンツの共有範囲、許容できる更新遅延、初期画面に必要な情報、運用コストを併せて考えてみてください。これらの条件を先に決めると、レンダリング方式とキャッシュ方針をより具体的に設計できます。

参考資料

以下の公式ドキュメントを基に、基本動作と API を確認しました。活用例とチェック項目は、それらを踏まえてまとめた設計上の提案です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?