SSR/SSG
サーバーサイドレンダリングと静的サイト生成 - Web ページの生成タイミングと場所を制御するレンダリング戦略
SSR/SSG とは
SSR (Server-Side Rendering) と SSG (Static Site Generation) は、ページの HTML をいつ、どこで組み立てるかを決めるレンダリング戦略である。戦略を見分ける軸はこの 2 つで、いつ (ビルド時か、リクエストのたびか) と、どこで (サーバーか、利用者の端末か) の組み合わせが、応答の速さとデータの鮮度を決める。
サーバー側で HTML を組み立てておくと、ブラウザに届いた時点で本文がマークアップに入っている。検索エンジンは JavaScript の実行が必要なページを描画の待ち行列に回すため、HTML に本文が入っていればその待ちを挟まずに済む (この事情は SPA 側で詳しく扱う)。
4 つのレンダリング戦略
| 戦略 | いつ生成するか | どこで生成するか | 用途 |
|---|---|---|---|
| CSR (Client-Side) | ブラウザでの実行時 | 利用者の端末 | 管理画面、検索流入を必要としない画面 |
| SSR (Server-Side) | リクエストのたび | サーバー | ダッシュボード、検索結果 |
| SSG (Static Site) | ビルド時 | ビルドを実行するマシン | ブログ、ドキュメント、LP |
| ISR (Incremental Static) | ビルド時 + 期限切れ後のリクエストを契機に再生成 | ビルドマシンとサーバーの両方 | EC サイト、ニュース |
4 つは排他ではない。同じアプリケーションの中でページ単位に選べるため、「このサイトは SSG」ではなく「一覧は SSG、注文履歴は SSR」という粒度で決めるのが実際の設計になる。
SSR (Server-Side Rendering)
リクエストを受けてからサーバーでデータを取得し、その場で HTML を組み立てて返す。
// Next.js App Router: Server Component (デフォルトで SSR)
async function ProductPage({ params }: { params: { id: string } }) {
const product = await db.products.findById(params.id); // サーバーで実行
return <div><h1>{product.name}</h1><p>{product.description}</p></div>;
}
常に最新のデータを表示できる代わりに、サーバーの処理時間がそのまま応答時間に乗る。データ取得に 300 ミリ秒かかるなら、最初のバイトが届くまでにその分の待ちが加わる。利用者ごとに内容が変わるページは CDN での共有キャッシュも効かないため、遅さがそのまま全リクエストに残る。
SSG (Static Site Generation)
ビルド時に全ページ分の HTML を生成しておき、配信時は出来上がったファイルを返すだけにする。
// Next.js: ビルド時に HTML を生成
export async function generateStaticParams() {
const posts = await db.posts.findMany();
return posts.map(post => ({ slug: post.slug }));
}
async function BlogPost({ params }: { params: { slug: string } }) {
const post = await db.posts.findBySlug(params.slug);
return <article><h1>{post.title}</h1><div>{post.content}</div></article>;
}
リクエスト時にアプリケーションコードが動かないので、配信は静的ファイルの取り出しに還元でき、S3 + CloudFront のような構成に載せられる。代償は 2 つある。データを更新してもビルドし直すまで古い内容が出続けること、そしてビルド時間がページ数に比例して伸びることである。ページ数が数千から数万の規模になると、1 ページの誤字修正でも全ページ分のビルドを待つことになる。
ISR (Incremental Static Regeneration)
配信形態は SSG のまま、生成し直す契機をビルドからリクエストへ移す仕組みである。
// Next.js: 再生成を 60 秒に 1 回までに制限する
export const revalidate = 60;
async function ProductPage({ params }) {
const product = await db.products.findById(params.id);
return <div>{product.name}: ¥{product.price}</div>;
}
revalidate = 60 は「60 秒ごとに再生成する」設定ではなく、「再検証をリクエスト契機で行い、その間隔を 60 秒に 1 回までに制限する」設定である。この違いが挙動に出る。期限が切れた後の最初のリクエストには古い HTML がそのまま返り、その裏側で再生成が走って、次のリクエストから新しい HTML に入れ替わる。したがってアクセスが途絶えている間は期限を過ぎても再生成されず、古さは経過時間ではなくアクセスの有無で決まる。在庫数や価格のように 1 リクエスト分の遅れが実害になる値は、この方式の対象から外して都度取得する。
ハイドレーションのコスト
HTML が早く届くことと、押して反応することは別の話である。SSR や SSG で本文が先に表示されても、ブラウザが同じコンポーネントをもう一度実行してイベントハンドラーを結び付ける処理 (ハイドレーション) が終わるまで、ボタンは見えているのに反応しない。
そのため転送するものは減らず、HTML と、同じ画面を組み立て直すための JavaScript の両方を送ることになる。表示が早くなった分だけ、操作できない時間のほうが目に付きやすくなる。この時間を削る方向は 2 つで、クライアントへ JavaScript を送らないコンポーネントを増やすか (Server Components)、対話が必要な部分だけを個別にハイドレートするかである。初回表示の速さと操作できるようになるまでの時間は別々の指標で測る必要があり、LCP が捉えるのは前者だけである。
選択基準
| ケース | 推奨戦略 |
|---|---|
| ブログ、ドキュメント | SSG (S3 + CloudFront) |
| EC サイト (商品ページ) | ISR (1 リクエスト分の遅れを許せる項目に限る) |
| ダッシュボード | SSR (常に最新データ) |
| 管理画面 | CSR (検索流入が不要) |
AWS でのホスティング
静的ファイルを配りたいのか、リクエストごとにアプリケーションを動かしたいのかで、必要な部品が変わる。
| 戦略 | 必要なもの | 構成例 |
|---|---|---|
| SSG / CSR | 静的ファイルの配信 | S3 + CloudFront |
| SSR | リクエストごとにアプリケーションを実行する環境 | Amplify Hosting、ECS / Fargate、Lambda |
| ISR | 上記 + 再生成した HTML を共有する保存先 | Amplify Hosting、ECS / Fargate + 共有キャッシュ |
ISR で実行環境を複数インスタンスに増やす場合、再生成した HTML をインスタンスの中に抱えると、同じ URL でも到達したインスタンスによって内容が変わる。共有の保存先に置くところまでが構成に含まれる。なお CloudFront に組み合わせる Lambda@Edge や CloudFront Functions は、リクエストとレスポンスに手を入れるための部品であり、フレームワークの SSR をそのまま載せる場所ではない。フレームワーク側の対応状況は Next.js を参照。
SSR/SSG については関連書籍でも詳しく扱われている。
この記事は役に立ちましたか?
関連用語
SPA
ページ遷移なしに動的にコンテンツを更新する Web アプリケーションのアーキテクチャ
Next.js
React ベースのフルスタックフレームワークで、SSR、SSG、API Routes を統合的に提供する
LCP
ビューポート内の最大コンテンツ要素が表示されるまでの時間を測定する Core Web Vitals の指標
SPA ルーティングとは - React/Vue/Angular でのページ遷移の仕組み
SPA ルーティングは URL とコンポーネント表示を同期させページ遷移なしで画面を切り替える仕組み。History API / Hash モード / SSR との関係を解説
HTML
Web ページの構造を記述するマークアップ言語。Web を成り立たせる基礎技術
Service Worker
ブラウザとネットワークの間でプロキシとして動作し、オフライン対応やキャッシュ制御を実現する Web API