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 固有の挙動で、ここがユーザー体験上のトレードオフ。
-
01 URL 入力 / リンククリック
ユーザーが URL を入力、もしくは既存ページのリンクをクリック。MPA の場合、このアクションは「新しいページ = 新しいドキュメント」を要求する合図になる。
-
02 全リソース破棄 + HTTP リクエスト
ブラウザは現在のページの DOM / JS 実行状態 / メモリを破棄し、新規リクエストを発行する。これが MPA 最大の特徴で、SPA のように「JS の中で状態を保持しながら UI を差分更新する」のとは対照的。View Transitions API が登場するまで、この「リセット」は MPA の UX 上の代償だった。
-
03 DNS / TCP / TLS
ネットワーク層で DNS 解決 → TCP 接続 → TLS ハンドシェイク。ただし同一オリジン内の MPA 遷移では Keep-Alive によって TCP / TLS を再利用でき、DNS キャッシュも効く。これが MPA の実際の体感速度を支えている。
-
04 サーバーで HTML を完全生成
サーバー側で DB クエリ、セッション / 認証チェック、テンプレートエンジン (Rails ERB, Django template, Laravel Blade 等) によって HTML を完成形で組み立てる。web.dev の定義では「server-side rendering = サーバーで HTML を生成してクライアントに JavaScript ではなく HTML を送る」と整理されていて、MPA はこの形のもっとも伝統的な形態。
-
05 完成 HTML を転送
サーバーが生成した HTML + linked CSS / images を HTTP レスポンスで転送。SPA の「空 HTML シェル → JS bundle」という 2 段階と違い、MPA は「完成品を 1 回で送る」というシンプルな設計。
-
06 HTML parse → DOM / CSSOM / Layout
ブラウザが HTML をパースして DOM を構築、CSS を CSSOM に、合成してレンダーツリーを作り、Layout (要素の幾何計算) を実行。MPA では HTML 内にすでにコンテンツ全体が入っているので、このフェーズが「本番の描画準備」になる。
-
07 Raster + レイヤー合成
コンポジタースレッドがタイルをラスタライズし、レイヤーツリーから合成コマンドを組み立てる。SPA の「1 回目: 空 → 2 回目: JS 後」という 2 回サイクルと違い、MPA はここが 1 回目で唯一の描画サイクル。
-
08 GPU でフレーム生成
GPU が合成コマンドを実行してフレームバッファを生成。ここまでに JS が 1 行も実行されていなくても、フル HTML が届いていればコンテンツは完成している。
-
09 画面にコンテンツ表示 — FCP / LCP
vsync に同期してフレームが画面に出力。ユーザーはここで初めて実コンテンツを見る。MPA は JS 実行を待たないので、FCP / LCP が構造的に速くなりやすい。Core Web Vitals の初回指標では伝統的に有利。
- FCP / LCP 到達 (JS なしで)
-
10 (任意) JavaScript 実行 — Progressive Enhancement
インタラクティブな機能 (フォームバリデーション、ドロップダウン等) のために JavaScript が実行される場合がある。MPA の思想としては「JS が無効でも動く HTML を先に作り、JS で拡張する (Progressive Enhancement)」が伝統。モダン MPA では Astro Islands や Hotwire Turbo / HTMX のように、「ページ全体は MPA、一部だけ動的」という使い方が増えている。
- TTI 到達 — JS が必要ない部分は Paint 時点で既に操作可能
-
11 リンククリック → ① に戻る (フルナビゲーション)
次のページへ遷移するリンクをクリックすると、フェーズ 02 のリソース破棄からやり直し。このたびに DOM / JS 状態がリセットされるのが MPA の特性。モダン MPA では View Transitions API や Turbo / Astro の特殊なルーティングでこの「リセット感」を緩和できる。
よくある疑問
「MPA って古い? 」「SSR と何が違う? 」など、MPA を学ぶ過程で出やすい疑問を整理。
Q. そもそも「MPA」って正式な用語なの? MDN にも Wikipedia にも単独エントリが無いけど
Q. MPA と SSR って何が違うの? 両方とも「サーバーで HTML を作る」だよね?
Q. 「モダン MPA」って具体的に何?
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 で状態管理ってどうするの?
?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?
client:load / client:visible 等の指示を付けたものだけが JS ランタイムを持ち込み (Islands アーキテクチャ)、残りは純粋な静的 HTML として配信される。View Transitions API でページ遷移も滑らかにできるので、UX 的には SPA との境界が曖昧になる場面もあるが、アーキテクチャとしては MPA 側。「SPA に疲れたけど UX は妥協したくない」というニーズに対する現代的な答えの 1 つ。Q. SEO や Core Web Vitals で MPA は SPA より有利?
設計判断のサイン
MPA が得意なケースと、MPA を卒業して SPA / SSR に移るべきサイン。
コンテンツ中心のサイト
ブログ・ニュース・ドキュメント・EC の商品ページ・メディアサイト等。SEO / OGP / 初回表示性能が最優先で、ユーザーは「1 ページ読んで次へ」が基本行動パターン。こうしたユースケースは MPA の一番得意な領域。Astro / Hugo / Jekyll のような SSG は MPA の延長線上。
JS ランタイムコストを最小化したい
モバイル / 低スペック端末 / 回線の弱い地域をターゲットにする場合、MPA の「JS ほぼゼロでも動く」特性が強く効く。Progressive Enhancement の思想で、JS 無効でもコア機能が使えるようにしておけば、3G 回線や古い端末でも離脱率が激減する。
チームが MPA 時代の資産を持っている
Rails / Django / Laravel / ASP.NET MVC 等のチームは、そのまま MPA で作るのが最も速い。Hotwire (Rails) / HTMX を足せば現代的な UX も確保できる。「SPA にする = JS 専業チームを抱える」コストを回避できる選択肢として、近年再評価されている。
同一画面内で UI が頻繁に変わる
ドラッグ&ドロップでのボード編集、マインドマップ、コラボレーションエディタ、音声通話中の動的 UI 等。1 画面内の操作で毎回ページ全体を再取得するのは非現実的。SPA や SPA + SSR (Next.js 等) が適切。
リアルタイム更新が中心
Slack 風チャット、ライブダッシュボード、通知が飛び交うアプリ等。WebSocket / Server-Sent Events で継続的にサーバーから push を受け取り、ページ全体ではなく一部だけ更新する必要がある。MPA でも可能だが、SPA の方が素直。
オフライン動作が必要
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 を入れるか」のルールをチーム内で決めておく。