レンダリング

MPA (Multi-Page Application)

MPA (Multi-Page Application) は、URL ごとにサーバーから完成した HTML を返す Web の伝統的なアーキテクチャ。Wikipedia・Amazon・GitHub・ニュースサイト等、現在もウェブの大半を占める。View Transitions API や Hotwire / Astro 等で再評価されている。

先に押さえる指標: 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 に置き換わったが、実ユーザーの「使えるようになる時刻」として依然重要。

レンダリングライフサイクル

MPA の 11 フェーズ。SPA の「空画面 Paint → JS 実行 → コンテンツ Paint」という 2 回描画サイクルが無く、完成 HTML を 1 回で受け取って 1 回で描画するシンプルな構造。ハイドレーションも発生しない。ただし 11 でリンクをクリックすると 02 (リソース破棄 + 新規リクエスト) に戻るのが MPA 固有の挙動で、ここがユーザー体験上のトレードオフ。

User
Browser
Network
Server
JS Engine
Compositor
GPU
Display
  1. 01

    URL 入力 / リンククリック

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

    ユーザーが URL を入力、もしくは既存ページのリンクをクリック。MPA の場合、このアクションは「新しいページ = 新しいドキュメント」を要求する合図になる。

  2. 02

    全リソース破棄 + HTTP リクエスト

    02 Browser 全リソース破棄 + HTTP リクエスト

    ブラウザは現在のページの DOM / JS 実行状態 / メモリを破棄し、新規リクエストを発行する。これが MPA 最大の特徴で、SPA のように「JS の中で状態を保持しながら UI を差分更新する」のとは対照的。View Transitions API が登場するまで、この「リセット」は MPA の UX 上の代償だった。

  3. 03

    DNS / TCP / TLS

    03 Network DNS / TCP / TLS

    ネットワーク層で DNS 解決 → TCP 接続 → TLS ハンドシェイク。ただし同一オリジン内の MPA 遷移では Keep-Alive によって TCP / TLS を再利用でき、DNS キャッシュも効く。これが MPA の実際の体感速度を支えている。

  4. 04

    サーバーで HTML を完全生成

    04 Server サーバーで HTML を完全生成

    サーバー側で DB クエリ、セッション / 認証チェック、テンプレートエンジン (Rails ERB, Django template, Laravel Blade 等) によって HTML を完成形で組み立てる。web.dev の定義では「server-side rendering = サーバーで HTML を生成してクライアントに JavaScript ではなく HTML を送る」と整理されていて、MPA はこの形のもっとも伝統的な形態。

  5. 05

    完成 HTML を転送

    05 Network 完成 HTML を転送

    サーバーが生成した HTML + linked CSS / images を HTTP レスポンスで転送。SPA の「空 HTML シェル → JS bundle」という 2 段階と違い、MPA は「完成品を 1 回で送る」というシンプルな設計。

  6. 06

    HTML parse → DOM / CSSOM / Layout

    06 Browser HTML parse → DOM / CSSOM / Layout

    ブラウザが HTML をパースして DOM を構築、CSS を CSSOM に、合成してレンダーツリーを作り、Layout (要素の幾何計算) を実行。MPA では HTML 内にすでにコンテンツ全体が入っているので、このフェーズが「本番の描画準備」になる。

  7. 07

    Raster + レイヤー合成

    07 Compositor Raster + レイヤー合成

    コンポジタースレッドがタイルをラスタライズし、レイヤーツリーから合成コマンドを組み立てる。SPA の「1 回目: 空 → 2 回目: JS 後」という 2 回サイクルと違い、MPA はここが 1 回目で唯一の描画サイクル。

  8. 08

    GPU でフレーム生成

    08 GPU GPU でフレーム生成

    GPU が合成コマンドを実行してフレームバッファを生成。ここまでに JS が 1 行も実行されていなくても、フル HTML が届いていればコンテンツは完成している。

  9. 09

    画面にコンテンツ表示 — FCP / LCP

    09 Display 画面にコンテンツ表示 — FCP / LCP

    vsync に同期してフレームが画面に出力。ユーザーはここで初めて実コンテンツを見る。MPA は JS 実行を待たないので、FCP / LCP が構造的に速くなりやすい。Core Web Vitals の初回指標では伝統的に有利。

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

    (任意) JavaScript 実行 — Progressive Enhancement

    10 JS Engine (任意) JavaScript 実行 — Progressive Enhancement

    インタラクティブな機能 (フォームバリデーション、ドロップダウン等) のために JavaScript が実行される場合がある。MPA の思想としては「JS が無効でも動く HTML を先に作り、JS で拡張する (Progressive Enhancement)」が伝統。モダン MPA では Astro Islands や Hotwire Turbo / HTMX のように、「ページ全体は MPA、一部だけ動的」という使い方が増えている。

  12. TTI 到達 — JS が必要ない部分は Paint 時点で既に操作可能
  13. 11

    リンククリック → ① に戻る (フルナビゲーション)

    11 User リンククリック → ① に戻る (フルナビゲーション)

    次のページへ遷移するリンクをクリックすると、フェーズ 02 のリソース破棄からやり直し。このたびに DOM / JS 状態がリセットされるのが MPA の特性。モダン MPA では View Transitions API や Turbo / Astro の特殊なルーティングでこの「リセット感」を緩和できる。

よくある疑問

「MPA って古い? 」「SSR と何が違う? 」など、MPA を学ぶ過程で出やすい疑問を整理。

Q. そもそも「MPA」って正式な用語なの? MDN にも Wikipedia にも単独エントリが無いけど
「MPA」は業界慣用語で、MDN / W3C / Wikipedia のいずれにも正式な単独定義は存在しない。これは「古くて無視されている」のではなく、MPA が Web のデフォルト = わざわざ名前をつけて区別する必要が無い基準線 だから。SPA が Glossary に入っているのは「それ以外」として名付けが必要になったから。技術的な定義を借りるなら web.dev「Rendering on the Web」の server-side rendering (「サーバーでアプリをレンダリングして、JavaScript ではなく HTML をクライアントに送信する」) や static rendering (「URL ごとに個別の HTML ファイルを事前に生成」) の分類が MPA の技術的基盤にあたる。Wikipedia の SPA 記事でも「traditional website」「classic page redraw model」といった対比的表現として扱われている。実態としては Wikipedia / Amazon / GitHub / 各種ニュース・EC・政府サイトは今も MPA で動いていて、2023 年あたりから「SPA 疲れ」を背景にモダン MPA (Astro / Hotwire Turbo / HTMX / 37signals) が明確に再評価されている。
Q. MPA と SSR って何が違うの? 両方とも「サーバーで HTML を作る」だよね?
重なる部分が大きいので混同されがちだが、ニュアンスが違う。MPA は「アーキテクチャのスタイル」 — ページごとに独立した HTML ドキュメントを返し、URL 遷移 = 新しいドキュメント要求、という設計。SSR は「レンダリングのタイミング (テクニック)」 — HTML をサーバー側で生成する処理そのものを指す。従来の MPA はほぼ全部 SSR だが、Next.js / Remix / SvelteKit のように SSR + クライアントサイドルーティング (= 遷移時に DOM を破棄しない SPA 的挙動) を組み合わせることもできる。つまり「MPA ⊂ SSR を使う設計」という関係性。
Q. 「モダン MPA」って具体的に何?
従来の MPA の弱点 (遷移時の白画面 / リソース再取得 / UI 状態の喪失) を、ブラウザ標準 API + 最小限の JS で埋めるアプローチ全般を指す。代表例: (1) Astro View Transitions: ブラウザの startViewTransition() API を使って、ページ遷移にアニメーションを付与。HTML ベースの MPA なのに SPA 風の遷移体験が出せる。(2) Hotwire Turbo (Rails): ページ遷移を fetch で AJAX 化して差分だけ更新、DOM を再利用して URL を更新。サーバー側は普通の MPA を書くだけ。(3) HTMX: HTML 属性 (hx-get / hx-post) だけで部分的なサーバー更新をフェッチ。JS をほぼ書かずに動的 UI を構築。(4) 37signals の ONCE: MPA 前提で作られた RoR 製 SaaS 群。共通して「JS のエコシステムに依存せず、サーバー + HTML を主役にする」思想。
Q. フォームの入力途中とか、MPA で状態管理ってどうするの?
MPA では「ページ遷移 = 新しいセッション」が基本なので、状態を明示的に保存するのが前提。選択肢: (1) URL クエリパラメータ (?filter=active&page=3&sort=date): 共有・ブックマーク可能、ブラウザバック対応。検索条件・ページング・ソート等に最適。(2) サーバーセッション (cookie + サーバー側の DB / Redis): ログイン状態・カート等。(3) sessionStorage / localStorage: フォーム自動保存、タブ固有の UI 状態。(4) hidden form field: 複数ステップフォームの中間データ。SPA が「メモリ上でなんとなく保つ」のと対照的に、MPA は「保存先を意識的に選ぶ」設計になる。これが MPA が堅牢と言われる理由の 1 つ。
Q. Astro って SPA? それとも MPA?
Astro は明確に 「MPA デフォルト + 必要な部分だけ SPA (Islands)」 という立場。デフォルトの挙動は各ページがサーバー側で生成されたフル HTML (= MPA)。コンポーネントのうち client:load / client:visible 等の指示を付けたものだけが JS ランタイムを持ち込み (Islands アーキテクチャ)、残りは純粋な静的 HTML として配信される。View Transitions API でページ遷移も滑らかにできるので、UX 的には SPA との境界が曖昧になる場面もあるが、アーキテクチャとしては MPA 側。「SPA に疲れたけど UX は妥協したくない」というニーズに対する現代的な答えの 1 つ。
Q. SEO や Core Web Vitals で MPA は SPA より有利?
初回表示系の指標 (FCP / LCP) では構造的に有利。完成 HTML を返すので JS 実行を待たずに描画でき、クローラーも JS 実行不要。TTI / INP (操作可能指標) は「JS がほぼ無い」MPA なら非常に速いが、モダン MPA で Turbo や Islands を入れるとその分だけ近づいていく。ただし「ページ間の連続操作」は SPA の方が速く感じる — MPA は毎回サーバーに完全 HTML を要求するので、SaaS のように「同一画面内で細かく UI が変わる」ユースケースでは SPA が有利。言い換えると、初回が勝負ならほぼ MPA、継続操作が勝負なら SPA、のスパース的な使い分け。

設計判断のサイン

MPA が得意なケースと、MPA を卒業して SPA / SSR に移るべきサイン。

MPA が適切

コンテンツ中心のサイト

ブログ・ニュース・ドキュメント・EC の商品ページ・メディアサイト等。SEO / OGP / 初回表示性能が最優先で、ユーザーは「1 ページ読んで次へ」が基本行動パターン。こうしたユースケースは MPA の一番得意な領域。Astro / Hugo / Jekyll のような SSG は MPA の延長線上。

MPA が適切

JS ランタイムコストを最小化したい

モバイル / 低スペック端末 / 回線の弱い地域をターゲットにする場合、MPA の「JS ほぼゼロでも動く」特性が強く効く。Progressive Enhancement の思想で、JS 無効でもコア機能が使えるようにしておけば、3G 回線や古い端末でも離脱率が激減する。

MPA が適切

チームが MPA 時代の資産を持っている

Rails / Django / Laravel / ASP.NET MVC 等のチームは、そのまま MPA で作るのが最も速い。Hotwire (Rails) / HTMX を足せば現代的な UX も確保できる。「SPA にする = JS 専業チームを抱える」コストを回避できる選択肢として、近年再評価されている。

MPA を卒業

同一画面内で UI が頻繁に変わる

ドラッグ&ドロップでのボード編集、マインドマップ、コラボレーションエディタ、音声通話中の動的 UI 等。1 画面内の操作で毎回ページ全体を再取得するのは非現実的。SPA や SPA + SSR (Next.js 等) が適切。

MPA を卒業

リアルタイム更新が中心

Slack 風チャット、ライブダッシュボード、通知が飛び交うアプリ等。WebSocket / Server-Sent Events で継続的にサーバーから push を受け取り、ページ全体ではなく一部だけ更新する必要がある。MPA でも可能だが、SPA の方が素直。

MPA を卒業

オフライン動作が必要

Service Worker を駆使した PWA では、オフライン時にも動作する UI を作りたい。MPA + Service Worker でもできなくはないが、サーバーレスポンスに依存せず UI ロジックが完結する SPA のほうが実装が楽。モバイルアプリに近い体験を目指す場合は SPA が素直。

よくある落とし穴

ページ遷移で UI 状態が消える

フォーム入力中に別ページへ → 戻ってきたら消えている、というのは MPA の典型的な苦情。対策: (a) beforeunload で警告、(b) 入力ごとに sessionStorage に自動保存、(c) ページ遷移前にサーバーへ下書き保存、(d) URL クエリに中間状態を持たせる。Hotwire Turbo や View Transitions API を使うと、そもそも DOM を保持したまま遷移できるのでこの問題が軽減する。

遷移時の白画面 (FOUC 風) で体感が悪い

ページ遷移のたびに一瞬真っ白になる「チラつき」は古典的な問題。対策: (a) CSS の page-transition-* / View Transitions API で遷移アニメーションを付ける、(b) instant.page / Quicklink で次ページをプリフェッチ、(c) Turbo / Astro のクライアントサイドルーティングを入れる。これらを組み合わせるだけで MPA の体感速度は劇的に改善する。

CDN キャッシュとパーソナライズの両立が難しい

ログイン状態によって HTML が変わる MPA を CDN でキャッシュすると、別ユーザーのページが漏れる。対策: (a) Cache-Control: private / Vary: Cookie、(b) ログイン固有部分 (ヘッダーのユーザー名等) だけを CSR / Edge Function で非同期挿入、(c) Edge Side Includes (ESI) でエッジ層でパーソナライズ部分だけ差し替え。SSG のようにシンプルにはならない。

JS の組み込み方がアドホックになりがち

「MPA だから JS はほぼ不要」と思っていると、ちょっとした動的挙動のたびに <script> タグや jQuery 風スニペットを各ページに散りばめることになり、保守性が劣化する。対策: 最初から Hotwire / HTMX / Astro Islands のような「MPA 前提の JS 組み込み方」を採用し、「どこに JS を入れるか」のルールをチーム内で決めておく。