SSG (Static Site Generator)
SSG (Static Site Generator) は、ビルド時に全ての HTML をあらかじめ生成して静的ファイルとして配信する仕組み。リクエストごとのサーバー処理が不要で、CDN から高速に配信できる。
MDN Web Docs より
静的サイトジェネレーター (SSG) は、静的なウェブサイトを生成するために使用されるソフトウェアです。静的ウェブサイトは、HTML、CSS、およびJavaScriptファイルで構成されます。最も重要なのは、静的サイトにはサーバーサイドロジックがないため、特定のURLに対してすべてのユーザーが同じコンテンツを受け取ることです。作成者は、Markdown、reStructuredText、HTML、(そして時にはReactなど)といった、ジェネレーターが受け入れる任意の形式でコンテンツを記述し、ジェネレーターはそれらをブラウザーでレンダリングできる最適化された静的ファイルのセットにコンパイルします。
静的サイトは、ブログ、ドキュメント、およびサーバーサイドで取得または生成する必要のあるデータを持たない、その他のコンテンツ駆動型ウェブサイトで一般的に使用されます。これらは高速で安全であり、CDNから提供できるため、デプロイも容易です。
先に押さえる指標: FCP / LCP / TTI
この後のスイムレーン図には FCP / LCP / TTI のマイルストーンが登場する。それぞれが「ユーザー体験のどの瞬間」を測っているのかを先に整理しておくと、図の中で『なぜここが重要なのか』が腹落ちしやすい。
- FCP First Contentful Paint — 最初の視覚コンテンツ描画
- テキスト・画像・SVG・非空の Canvas のような「視覚的なコンテンツ」がブラウザ画面に最初に描画された時点。背景色のみの画面は含まれない。Core Web Vitals の補助指標で、1.8 秒以内が良好とされる。
- LCP Largest Contentful Paint — 最大コンテンツ描画
- ページ内で最大のコンテンツ要素 (ヒーロー画像・主要見出し等) が描画された時点。「意味のある初回描画」の代理指標で、Core Web Vitals の主指標。2.5 秒以内が良好、4 秒以上は要改善。
- TTI Time to Interactive — 操作可能になった時刻
- ページが完全にインタラクティブ (クリックや入力に反応できる) になった時点。全てのイベントリスナーが付与され、main thread が 5 秒間 Long Task を出さなくなるタイミング。2024 年に Core Web Vitals 主指標から外れ INP に置き換わったが、実ユーザーの「使えるようになる時刻」として依然重要。
レンダリングライフサイクル
SSG のライフサイクルは「ビルド時」と「ランタイム」の 2 フェーズに完全に分離されている。レーン構成も異なるため、それぞれを独立したスイムレーン図として整理する。SSR との決定的な違いは、ランタイム側でアプリケーションサーバーが一切動かない点。
コード push や手動トリガーから CDN デプロイ完了までの 7 フェーズ。ここで全データ取得・全 HTML 生成が完了する。
-
B1 npm run build 実行
開発者 or CI (GitHub Actions / Vercel / Netlify) がビルドコマンドを起動。Git の push や手動トリガーがきっかけ。
-
B2 全ルートを列挙
getStaticPaths/generateStaticParamsを実行して、ビルド対象の全ルートを確定。ブログ記事・商品・タグページ等もこの時点で決まる。 -
B3 データ取得の HTTP リクエスト
各ルートのデータ取得関数 (
getStaticProps/load/ Server Component の fetch) がビルド時に HTTP リクエストを発出。SSR と違って「ビルド時の 1 回きり」。 -
B4 DB / CMS / API からのレスポンス
データソースがビルド時のリクエストに応答。Headless CMS (Contentful / microCMS 等) や DB、外部 API がここで叩かれる。レート制限に注意。
-
B5 renderToString() を全ルート分
取得したデータを元に、各ルートの HTML を生成。Node.js 上で React / Vue / Svelte の SSR 関数をループ実行する。ページ数に比例してビルド時間が伸びる (1 万ページで 10 分級)。
-
B6 静的ファイルを dist/ へ出力
生成された HTML + CSS + JS + 画像が dist/ (あるいは build/) ディレクトリに書き出される。これが最終的な「配信物」。
-
B7 CDN へアップロード
成果物を Vercel Edge / Netlify / Cloudflare Pages / S3 + CloudFront 等にデプロイ。ここで「生成物」と「運用」が完全に切り離される。以降、ランタイムではアプリサーバーが一切動かない。
- ビルド完了 — 以降はランタイムで再実行されない
ユーザーのアクセス時に起きる 12 フェーズを 8 レーンに分解。アプリサーバーが一切介在せず、CDN → Browser (Style/Layout) → Compositor (Raster) → GPU (フレームバッファ) → Display (vsync 表示) で FCP / LCP に到達。JS は「必要なら任意で」後から走る。
-
R1 URL 入力 / リンククリック
ユーザーが URL を開く。リクエスト先はアプリサーバーではなく CDN。
-
R2 HTTP リクエスト発出
ブラウザが CDN 宛に HTTP リクエストを準備して送信。
-
R3 近いエッジロケーションへルーティング
CDN の Anycast で地理的に最も近いエッジに自動ルーティング。日本のユーザーなら東京エッジ、ヨーロッパならフランクフルト等。
-
R4 キャッシュ済み HTML を即返却
CDN エッジがキャッシュ済みの静的 HTML をそのまま返す。TTFB が 10〜50 ms 級。ここにはアプリケーションロジックが存在しないため、攻撃対象も極小。
-
R5 HTML パース → DOM / CSSOM
ブラウザが完成 HTML をパースして DOM を構築、並行して CSS を CSSOM に変換。SSR と同じく DOM には既にコンテンツが含まれている。
-
R6 Style / Layout / Paint-record
Style 計算 → Layout → Paint-record を実行。コンテンツ込みなので描画コマンドが豊富。
-
R7 Raster + レイヤー合成
コンポジタースレッドがタイルをラスタライズし、レイヤーツリーから合成コマンドを組み立てる。
-
R8 GPU でフレーム生成
GPU プロセスが合成コマンドを実行し、フレームバッファを構築。
-
R9 コンテンツが画面に表示 — JS 不要
vsync に同期してピクセルが画面に届く。ここまでに JavaScript は 1 行も実行されていない。CDN → ブラウザパイプライン → 画面、というシンプルな流れのみで到達できるのが SSG 最大の強み。
- FCP / LCP 到達 (JS なしで)
-
R10 JS バンドル (任意) の転送
インタラクティブな部分がある場合のみ、CDN から JS バンドルが転送される。完全に静的なサイトならこのステップ以降は不要で、FCP ≈ TTI になる。
-
R11 hydrateRoot() / 選択的ハイドレート
必要な部分だけハイドレーション。Astro の Islands Architecture では
client:load等で指定した島だけが JavaScript を評価。ページ全体のハイドレーションを避けることで JS の配信量を最小化できる。 - TTI 到達 — 静的サイトなら FCP ≈ TTI もあり得る
-
R12 動的データを CSR で fetch (任意)
ログインユーザー名・カート数・通知件数など「ユーザー固有 / リアルタイム」な要素はビルド時に知らないので、ハイドレーション後にクライアントが API を叩く (CSR)。あるいは Edge Function で個別処理。以降の DOM 変更は Browser → Compositor → GPU → Display のパイプラインで SPA 同様に反映される。
ISR: SSG の「更新できない」問題を解く
SSG の最大の弱点は「更新するたびに全体再ビルドが必要」なこと。ISR (Incremental Static Regeneration) はこれを緩和する仕組みで、Next.js が先行実装した後に多くのフレームワークが追従した。ページ単位で「次のリクエスト時に再生成する」と指示できる。
時間ベース (revalidate)
ページに「60 秒有効」のような有効期限を設定。期限切れ後の最初のアクセスは古い HTML を返しつつ、バックグラウンドで再生成 (stale-while-revalidate)。2 回目以降は新しい HTML。サーバー負荷が極小なのに「ちょっと新しい」を両立する定番パターン。
オンデマンド (on-demand)
CMS の記事公開 / 商品登録のタイミングで Webhook を叩き、該当ページだけ即再生成。ユーザーアクセスを待たないので反映ラグがほぼゼロ。CMS + SSG のワークフローで実用的な選択肢。
動的コンテンツをどう扱うか
SSG だけで全てを完結させようとすると破綻するケースが多い。現代の SSG は「大半を静的 + 動的部分は別の仕組みで補う」ハイブリッド運用が標準。代表的な 3 戦略。
CSR + Fetch (最も一般的)
静的 HTML で「器」を用意し、動的な部分はブラウザ側で useEffect から API を叩いて差し込む。カートバッジ・ログイン名・通知件数など小さい部分はこれで十分。注意点は (a) レイアウトシフト防止のスケルトン表示、(b) fetch 中のエラーハンドリング。
Edge Function で動的部分だけ処理
Vercel Edge Functions / Cloudflare Workers / Netlify Edge Functions で、特定のエンドポイントや HTML 断片だけを動的に処理。CDN のエッジで動くのでレイテンシが低く、認証チェック・A/B テスト・地域別コンテンツに向く。完全 SSR よりインフラ費用が予測しやすい。
Islands Architecture (Astro 等)
ページの大半は完全静的、インタラクティブな部分だけを「島」として JavaScript で動かす。各島は独立してハイドレートされるので、1 つの重い島が他の島をブロックしない。JS 配信量が劇的に減るため、SSG の「速さ」を最大限活かせる。
よくある疑問
SSG / ISR / JAMstack の関係や、実運用で必ず出る疑問を整理。
Q. SSR と SSG の本当の違いは? どっちも完成 HTML を返すよね?
Q. ISR は SSG とどう違うの? SSR とも違う?
Q. ログインが必要なページを SSG で作るのは無理?
Q. ビルド時間が長すぎて CI が詰まる。どうすれば?
Q. SSG のサイトを更新したのに古い内容が表示される?
Cache-Control: max-age を指定、(b) CDN キャッシュ → 再ビルド後に明示的に purge (Vercel / Netlify は自動、自前 CloudFront なら invalidation API)、(c) Service Worker → 古い cache-first 戦略で HTML を保持している場合。デバッグは Chrome DevTools の Network タブで「from disk cache / from memory cache / from ServiceWorker」を確認。Q. JAMstack と SSG って同じ意味?
設計判断のサイン
SSG が得意なケースと、無理に使うと破綻するケース。
コンテンツの大半が全ユーザー共通
ドキュメント、マーケティングサイト、ブログ、ポートフォリオ、OSS のランディング。ビルド時に作れるものはビルド時に作る — 運用中のサーバー処理を不要にできるので、SSG が最も得意なゾーン。
トラフィック変動が大きい / 予測できない
急激にアクセスが増えても、ビルド済みの静的 HTML が CDN から配信されるためサーバー側で計算は発生しない。web.dev「Rendering on the Web」が説明している通り、Static rendering は「fast First Paint, First Contentful Paint and Time To Interactive」を提供する性質を持ち、CDN による配信に適している。SSR ではリクエストごとにアプリサーバーで計算するため、トラフィック増加時はサーバーのスケーリング (オートスケール / コールドスタート) を別途設計する必要がある。
1 日 1 回以上の頻度でコンテンツが変わる
ニュース、SNS 風タイムライン、EC の在庫 / 価格のように頻繁に変わる要素は、毎回再ビルドするのが非現実的。ISR で救えることもあるが、1 日 10 回以上の更新になると SSR のほうが素直。
ユーザー固有内容が主要
ログイン後のダッシュボード、カート、個人設定ページのような「全員違う画面」は SSG では作れない (作れても CSR の後処理に頼ることになる)。設計思想として SPA または SSR のほうが適切。
よくある落とし穴
ページ数の爆発でビルドが破綻する
記事・商品ページが数万・数十万スケールになると、純粋な SSG はビルドだけで 1〜数時間かかる。対策: ISR で「アクセスされたときに作る」に切り替える、または「人気ページだけビルド、レアページは初回アクセス時」という分割戦略 (Next.js の fallback: true / blocking)。画像変換の並列化だけでも時短効果は大きい。
ビルド時 API のレート制限に引っかかる
全ページ分のデータをビルド時に取得すると、CMS や外部 API のレート制限を 1 回のビルドで使い切ることがある。対策: (a) ビルドキャッシュで「変更されたページだけ再取得」、(b) バッチ API があれば使う、(c) 事前に SaaS 側と相談して quota を引き上げる、(d) ISR で分散させる。
認証必須コンテンツを SSG で作ろうとする
SSG の HTML は基本的に全員に公開される。認証ページを SSG で作ると中身が誰でも見える危険性がある。対策: (a) 認証チェックはクライアント JavaScript / Edge Function で、HTML 自体は空の殻にする、(b) 認証が必要な API は別途サーバーで保護する、(c) そもそも「公開してよい HTML」と「公開してはいけないデータ」を明確に分離する設計にする。
更新反映のラグを制御できない
CMS で記事を更新してもサーバーの古い HTML と CDN キャッシュが残り、ユーザーに反映されるまで数分〜数十分かかる。対策: (a) CMS の公開イベントから Webhook でビルドをトリガー、(b) ISR のオンデマンド再生成を使う、(c) CDN の purge API を叩く (Cloudflare / CloudFront)。「いつ反映されるか」が運用の明確な SLA になっていることが大事。