レンダリング

SSG (Static Site Generator)

SSG (Static Site Generator) は、ビルド時に全ての HTML をあらかじめ生成して静的ファイルとして配信する仕組み。リクエストごとのサーバー処理が不要で、CDN から高速に配信できる。

MDN Web Docs より

静的サイトジェネレーター (SSG) は、静的なウェブサイトを生成するために使用されるソフトウェアです。静的ウェブサイトは、HTMLCSS、および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 との決定的な違いは、ランタイム側でアプリケーションサーバーが一切動かない点。

ビルド時 (1 回きり)

コード push や手動トリガーから CDN デプロイ完了までの 7 フェーズ。ここで全データ取得・全 HTML 生成が完了する。

Developer / CI
Build (Node.js)
Data Source
DB / CMS / API
Storage (CDN)
  1. B1

    npm run build 実行

    B1 Developer / CI npm run build 実行

    開発者 or CI (GitHub Actions / Vercel / Netlify) がビルドコマンドを起動。Git の push や手動トリガーがきっかけ。

  2. B2

    全ルートを列挙

    B2 Build (Node.js) 全ルートを列挙

    getStaticPaths / generateStaticParams を実行して、ビルド対象の全ルートを確定。ブログ記事・商品・タグページ等もこの時点で決まる。

  3. B3

    データ取得の HTTP リクエスト

    B3 Data Source データ取得の HTTP リクエスト

    各ルートのデータ取得関数 (getStaticProps / load / Server Component の fetch) がビルド時に HTTP リクエストを発出。SSR と違って「ビルド時の 1 回きり」。

  4. B4

    DB / CMS / API からのレスポンス

    B4 DB / CMS / API DB / CMS / API からのレスポンス

    データソースがビルド時のリクエストに応答。Headless CMS (Contentful / microCMS 等) や DB、外部 API がここで叩かれる。レート制限に注意。

  5. B5

    renderToString() を全ルート分

    B5 Build (Node.js) renderToString() を全ルート分

    取得したデータを元に、各ルートの HTML を生成。Node.js 上で React / Vue / Svelte の SSR 関数をループ実行する。ページ数に比例してビルド時間が伸びる (1 万ページで 10 分級)。

  6. B6

    静的ファイルを dist/ へ出力

    B6 Build (Node.js) 静的ファイルを dist/ へ出力

    生成された HTML + CSS + JS + 画像が dist/ (あるいは build/) ディレクトリに書き出される。これが最終的な「配信物」。

  7. B7

    CDN へアップロード

    B7 Storage (CDN) CDN へアップロード

    成果物を Vercel Edge / Netlify / Cloudflare Pages / S3 + CloudFront 等にデプロイ。ここで「生成物」と「運用」が完全に切り離される。以降、ランタイムではアプリサーバーが一切動かない。

  8. ビルド完了 — 以降はランタイムで再実行されない
ランタイム (リクエストごと)

ユーザーのアクセス時に起きる 12 フェーズを 8 レーンに分解。アプリサーバーが一切介在せず、CDN → Browser (Style/Layout) → Compositor (Raster) → GPU (フレームバッファ) → Display (vsync 表示) で FCP / LCP に到達。JS は「必要なら任意で」後から走る。

User
Browser
Network
CDN Edge
JS Engine
Compositor
GPU
Display
  1. R1

    URL 入力 / リンククリック

    R1 User URL 入力 / リンククリック

    ユーザーが URL を開く。リクエスト先はアプリサーバーではなく CDN。

  2. R2

    HTTP リクエスト発出

    R2 Browser HTTP リクエスト発出

    ブラウザが CDN 宛に HTTP リクエストを準備して送信。

  3. R3

    近いエッジロケーションへルーティング

    R3 Network 近いエッジロケーションへルーティング

    CDN の Anycast で地理的に最も近いエッジに自動ルーティング。日本のユーザーなら東京エッジ、ヨーロッパならフランクフルト等。

  4. R4

    キャッシュ済み HTML を即返却

    R4 CDN Edge キャッシュ済み HTML を即返却

    CDN エッジがキャッシュ済みの静的 HTML をそのまま返す。TTFB が 10〜50 ms 級。ここにはアプリケーションロジックが存在しないため、攻撃対象も極小。

  5. R5

    HTML パース → DOM / CSSOM

    R5 Browser HTML パース → DOM / CSSOM

    ブラウザが完成 HTML をパースして DOM を構築、並行して CSS を CSSOM に変換。SSR と同じく DOM には既にコンテンツが含まれている。

  6. R6

    Style / Layout / Paint-record

    R6 Browser Style / Layout / Paint-record

    Style 計算 → Layout → Paint-record を実行。コンテンツ込みなので描画コマンドが豊富。

  7. R7

    Raster + レイヤー合成

    R7 Compositor Raster + レイヤー合成

    コンポジタースレッドがタイルをラスタライズし、レイヤーツリーから合成コマンドを組み立てる。

  8. R8

    GPU でフレーム生成

    R8 GPU GPU でフレーム生成

    GPU プロセスが合成コマンドを実行し、フレームバッファを構築。

  9. R9

    コンテンツが画面に表示 — JS 不要

    R9 Display コンテンツが画面に表示 — JS 不要

    vsync に同期してピクセルが画面に届く。ここまでに JavaScript は 1 行も実行されていない。CDN → ブラウザパイプライン → 画面、というシンプルな流れのみで到達できるのが SSG 最大の強み。

  10. FCP / LCP 到達 (JS なしで)
  11. R10

    JS バンドル (任意) の転送

    R10 Network JS バンドル (任意) の転送

    インタラクティブな部分がある場合のみ、CDN から JS バンドルが転送される。完全に静的なサイトならこのステップ以降は不要で、FCP ≈ TTI になる。

  12. R11

    hydrateRoot() / 選択的ハイドレート

    R11 JS Engine hydrateRoot() / 選択的ハイドレート

    必要な部分だけハイドレーション。Astro の Islands Architecture では client:load 等で指定した島だけが JavaScript を評価。ページ全体のハイドレーションを避けることで JS の配信量を最小化できる。

  13. TTI 到達 — 静的サイトなら FCP ≈ TTI もあり得る
  14. R12

    動的データを CSR で fetch (任意)

    R12 JS Engine 動的データを 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 戦略。

01

CSR + Fetch (最も一般的)

静的 HTML で「器」を用意し、動的な部分はブラウザ側で useEffect から API を叩いて差し込む。カートバッジ・ログイン名・通知件数など小さい部分はこれで十分。注意点は (a) レイアウトシフト防止のスケルトン表示、(b) fetch 中のエラーハンドリング。

02

Edge Function で動的部分だけ処理

Vercel Edge Functions / Cloudflare Workers / Netlify Edge Functions で、特定のエンドポイントや HTML 断片だけを動的に処理。CDN のエッジで動くのでレイテンシが低く、認証チェック・A/B テスト・地域別コンテンツに向く。完全 SSR よりインフラ費用が予測しやすい。

03

Islands Architecture (Astro 等)

ページの大半は完全静的、インタラクティブな部分だけを「島」として JavaScript で動かす。各島は独立してハイドレートされるので、1 つの重い島が他の島をブロックしない。JS 配信量が劇的に減るため、SSG の「速さ」を最大限活かせる。

よくある疑問

SSG / ISR / JAMstack の関係や、実運用で必ず出る疑問を整理。

Q. SSR と SSG の本当の違いは? どっちも完成 HTML を返すよね?
HTML を作るタイミングが決定的に違う。SSR は「リクエストが来るたびにサーバーで生成」、SSG は「ビルド時に一度だけ生成して CDN に置く」。結果として (a) TTFB: SSG > SSR、(b) 鮮度: SSR > SSG、(c) コスト: SSG < SSR (サーバー不要)、(d) セキュリティ攻撃面: SSG < SSR (ランタイムで動くコードが無い)。同じ HTML を返す最終形態でも、運用コストと障害耐性は別物。
Q. ISR は SSG とどう違うの? SSR とも違う?
ISR は SSG の拡張で、「基本は静的だが、一部ページを更新可能にする」仕組み。具体的には (a) 時間ベースで再生成 (revalidate: 60 など)、(b) オンデマンドで特定ページだけ再生成 (Webhook 経由) の 2 モード。SSR との違いは「毎リクエストで再生成しない」こと — 再生成は期限切れ時 / トリガー時のみ。SSG と SSR の中間で、Vercel や Netlify のホスティングに依存する機能。
Q. ログインが必要なページを SSG で作るのは無理?
工夫が必要。ログインフォーム自体は SSG で問題ない (全員同じ HTML)。ログイン後の個別データは以下のいずれかで対応: (a) ハイドレーション後にクライアントで API を叩いて取得 (CSR)、(b) Edge Function で認証チェック + 動的コンテンツ、(c) 完全にログイン後は別ドメインに切り出して SSR or SPA にする。よくあるのは (a) で、認証状態によって UI を差し替える。
Q. ビルド時間が長すぎて CI が詰まる。どうすれば?
選択肢は 3 つ。(1) ISR / オンデマンド で真に静的化が必要なページだけビルドし、残りは初回アクセス時に生成。(2) 差分ビルド に対応したフレームワーク / ビルドツール (Turbopack、Vite) を使う。(3) 画像最適化を別プロセスに分離 — 画像処理がビルド時間の大半を占めることが多く、Sharp などを並列化するだけで劇的に改善する。ページ数が数万を超える場合、純粋な SSG は再考時期。
Q. SSG のサイトを更新したのに古い内容が表示される?
CDN キャッシュが残っている可能性が高い。原因パターンと対策: (a) ブラウザキャッシュ → デプロイ時に HTML のファイル名にハッシュを付ける、もしくは短い 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 って同じ意味?
ほぼ重なるが厳密には違う。JAMstack は「JavaScript (クライアント) + API + Markup (事前ビルド)」というアーキテクチャパターンの総称で、SSG はそのうち「Markup の事前ビルド」を担う部分。JAMstack は「静的マークアップ + CDN 配信 + API で動的部分を賄う」という設計思想全体を指す。SSG + ISR + Edge Function の組み合わせも広義の JAMstack に含まれる。

設計判断のサイン

SSG が得意なケースと、無理に使うと破綻するケース。

SSG が適切

コンテンツの大半が全ユーザー共通

ドキュメント、マーケティングサイト、ブログ、ポートフォリオ、OSS のランディング。ビルド時に作れるものはビルド時に作る — 運用中のサーバー処理を不要にできるので、SSG が最も得意なゾーン。

SSG が適切

トラフィック変動が大きい / 予測できない

急激にアクセスが増えても、ビルド済みの静的 HTML が CDN から配信されるためサーバー側で計算は発生しない。web.dev「Rendering on the Web」が説明している通り、Static rendering は「fast First Paint, First Contentful Paint and Time To Interactive」を提供する性質を持ち、CDN による配信に適している。SSR ではリクエストごとにアプリサーバーで計算するため、トラフィック増加時はサーバーのスケーリング (オートスケール / コールドスタート) を別途設計する必要がある。

SSG は向かない

1 日 1 回以上の頻度でコンテンツが変わる

ニュース、SNS 風タイムライン、EC の在庫 / 価格のように頻繁に変わる要素は、毎回再ビルドするのが非現実的。ISR で救えることもあるが、1 日 10 回以上の更新になると SSR のほうが素直。

SSG は向かない

ユーザー固有内容が主要

ログイン後のダッシュボード、カート、個人設定ページのような「全員違う画面」は 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 になっていることが大事。