PESPA(Progressively Enhanced Single Page Apps)は、JavaScriptが動かなくても成立するWebアプリの土台に、SPAの操作感を後から重ねる設計方針です。同じ綴りのオフィス家具ブランドやスクーターとは関係のない、Webアーキテクチャの用語です。この記事では、提唱者の原典に沿った定義、MPA・PEMPA・SPAとの違い、そして2026年10月時点でPESPAを書ける実装先を扱います。実装先の選定では、React Routerへ続く系譜と、Reactに依存しないRemix 3を区別します。npmのremixパッケージは2026年10月1日(UTC)に3.0.0が正式版として公開されてlatestになり、Remix v2の系譜はReact Router v8が引き継いでいます。
まとめ
PESPAは、Remixの共同創業者だったKent C. Doddsが「The Web’s Next Transition」で示した呼び名です。第一要件は「動作することが基準線で、JavaScriptは機能を有効化するためでなく強化するために使う」こと。ただし原典が強調する最大の利点は「JavaScriptなしで動く」ことではなく、リクエストとレスポンスの往復で考えるMPAの単純なメンタルモデルを、SPAの体験のまま取り戻せる点にあります。PEMPAはPESPAが解消した前段階で、UIコードの手動二重化を生む構成として原典に名指しされています。
2026年10月時点の実装先は2系統です。Remix v2のコードを延長するならReact Router v8(latestは8.4.0)で、actionとFormが基準線を担います。移行ガイドはv7のドキュメントにしか残っていないため、v2からはv7を経由します。一方、正式版になったRemix 3.0.0はReactに依存せず、素のHTMLのformとaをブラウザのNavigation APIで横取りする方式に切り替えました。Navigation APIが無いブラウザでは通常の文書遷移に戻ります。Reactの資産を持つチームはReact Router v8、Reactに縛られない新規案件ならRemix 3が候補です。
PESPAの定義と原典の5要件
PESPAという略語は、Kent C. Doddsの記事The Web’s Next Transitionで提示されました。Epic Web Dev版のページの構造化データでは、公開日時が2022年10月4日、最終更新が2024年3月27日です。
原典が列挙するPESPAの5要件
原典はPESPAの特徴を次の5点にまとめています。原文の要点と訳を並べます。
- Functional is the baseline - JS used to enhance not enable(動作することが基準線。JavaScriptは有効化ではなく強化に使う)
- Lazy loading + intelligent pre-fetching (more than just JS code)(遅延読み込みと先読み。対象はJSコードに限らない)
- Pushes code to the server(コードをサーバー側へ寄せる)
- No manual duplication of UI code (as in PEMPAs)(PEMPAで起きるUIコードの手動二重化がない)
- Transparent browser emulation (#useThePlatform)(ブラウザの挙動を透過的に再現する)
「最小限のHTMLとCSSを先に返し、あとからJavaScriptを足す」という説明は、2003年に提唱されたプログレッシブエンハンスメント一般の話で、PESPA固有の定義ではありません。5点のうち1番目に部分的に重なるだけです。PESPAを名乗る条件は残りの4点、とりわけ「UIコードを二重に書かない」と「ブラウザの挙動を透過的に再現する」にあります。
主な利点はJavaScript不要ではなくメンタルモデルの単純化
原典はこの点を太字で強調しています。プログレッシブエンハンスメントの主な利点は「JavaScriptなしでアプリが動く」ことではなく(それは副次的な利点)、メンタルモデルが大幅に単純になることだ、という主張です。
MPAでは、フォームを送るとサーバーが処理し、ブラウザが次の画面を取りに行く、という1往復で全体を説明できました。SPAではこの往復をクライアント側の状態管理、キャッシュ、ローディング表示に分解して自前で組み立てます。PESPAは、フレームワークがブラウザのふるまいを肩代わりすることで、開発者の頭の中をMPAの1往復に戻します。設計を確かめるときは、JavaScriptを無効にして主要なリンク遷移とフォーム送信を試します。送信中の表示や楽観的更新のようにクライアントだけで動く部分が残ることは、原典も認めています。
MPA・PEMPA・SPA・PESPAの違い
4つの呼び名は登場順に並んでおり、「UIを描くコードがどこにあるか」と「JavaScriptが無いときに何が起きるか」で区別できます。SPAそのものの定義や開発言語の選び方はSPAとは?開発言語・フレームワークの選び方とMPA・SSRとの違い【2026年10月】で扱っています。
4アーキテクチャの対応表
| 種別 | UIを描くコード | JavaScript無効時 | UIコードの重複 | 代表例 |
|---|---|---|---|---|
| MPA | サーバーのみ | 全機能が動作 | なし | PHP、Rails |
| PEMPA | サーバー+クライアント | 基本機能は動作 | 手動で二重化 | jQuery+AJAX |
| SPA | 主にクライアント | 主要な機能が動かない | なし | React+REST API |
| PESPA | 同じコードを両側で実行 | 基本機能は動作 | なし | React Router、Remix 3、SvelteKit |
MPAとSPAは「UIコードが主に片側にある」点で同じで、置き場所が正反対なだけです。PEMPAとPESPAはどちらも中間を狙いますが、PEMPAは同じ画面を2回書く負債を抱え、PESPAは1つのコードを両側で走らせて負債を消します。原典は、Remixに続いてPESPAの原則を取り込んでいるフレームワークとしてSvelteKitとSolidStartの名前を挙げています。
選ぶときの線引きは単純です。検索流入が事業に直結する公開ページや、初回表示の速さが離脱に響く画面は、サーバーで完成したHTMLを返せる構成(MPAかPESPA)を前提にします。ログイン後に使い、ブラウザとネットワークを統制できる社内管理画面では、初回表示時間や送信失敗時の復旧を検証したうえでSPAを選べます。描画方式そのものの整理はレンダリングとは?ブラウザ描画の仕組みとCSR/SSR/SSGの違い・選定基準を解説にまとめています。
PEMPAはPESPAが解消した前段階
日本語の解説で見かける「フレームワークがMPAとPEMPAを統合してPESPAになった」という説明は、原典と逆です。原典が「SPAとMPAを1つのアーキテクチャにまとめて両方の良さを得る」と書いているのはPESPAのことで、PEMPAは「No manual duplication of UI code (as in PEMPAs)」と、避けるべき負債の例として名指しされています。
PEMPAは、サーバーでHTMLを組み立てるMPAに、部分更新のためのJavaScriptを後付けした形です。サーバー側テンプレートで書いた行の描画処理を、AJAXで追加する行のためにクライアント側でもう一度書きます。テンプレート言語とJavaScriptで同じ表示ロジックが二重になるため、片方の修正漏れがそのまま表示の食い違いになります。サーバーがHTML断片を返して差し替えるhtmxやHotwireは、クライアント側の描画コードを持たないことでこの二重化を避けており、jQuery時代のPEMPAとは事情が異なります。
SPAが自前で作り直したブラウザ機能
SPAで手間がかかるのは、初期表示の遅さよりも、ブラウザが本来持っていた機能の作り直しです。読み込み中の表示、再送信時の進行中リクエストのキャンセル、送信後のデータ再取得、進む・戻るの履歴、スクロール位置とフォーカスの管理。MPAではブラウザが無償で提供していたものが、SPAではすべてアプリ側の実装項目に変わります。PESPAの「Transparent browser emulation」は、この作り直しをフレームワークが引き受けるという宣言です。
PESPAのリクエスト処理:遷移・送信・再検証
原典は各アーキテクチャを、ドキュメントリクエスト、クライアント側の遷移、ミューテーション(データ更新)の3種類の通信で比べています。PESPAの振る舞いをこの3つで整理すると、実装を見たときに要件を満たしているかを判定できます。
ドキュメントリクエストとクライアント側遷移
最初のページ表示はPEMPAと同じで、サーバーが完成したHTMLを返し、あとから読み込んだJavaScriptが操作を強化します。リンクをクリックしたときは既定の遷移を止め、ルーターが次の画面に必要なデータとUIを取得して描画します。JavaScriptが読み込まれる前や失敗したときは、止める処理自体が動かないので、ブラウザが通常どおり次のページを取りに行きます。どちらの経路でも同じURLと同じサーバー処理に着地する点が、PESPAの基準線です。
文書遷移を伴う更新後のリダイレクト
原典はMPAの説明の中で、成功した更新は新しいHTMLを直接返すのではなくリダイレクトを返すべきだと注意しています。POSTの結果をそのまま表示すると履歴にPOSTが残り、戻るボタンで再送信が起きるからです。「戻るボタンを押さないでください」と表示するサイトが生まれる原因がこれで、POST→リダイレクト→GETの順にすれば防げます。PESPAでも画面遷移を伴う更新は同じ考え方で、フレームワークはJavaScriptで送信した場合もこのリダイレクトを解釈して画面を切り替えます。画面遷移を伴わない部分更新(いいねボタンのような操作)はリダイレクトせず、次に述べる再検証で画面を更新します。
送信後の自動再検証
MPAではフォーム送信後にページ全体を読み直すため、送信直後の画面にはそのレスポンス時点のデータが反映されます。原典はPESPAに、この「更新後にページ上のデータを取り直す」挙動をfetchで再現することを求めています。React Routerのactionsのドキュメントは、actionが完了するとページ上のすべてのloaderデータが再検証され、コードを書かずにUIとデータが同期されると説明しています。SPAで一覧キャッシュの無効化を書き忘れると「追加したはずの行が出ない」不具合になります。PESPAでは送信後の再検証でこの種のずれを減らせますが、再検証を省く設定やサーバー側のキャッシュによっては不整合が残ります。
React Router v8によるPESPAの実装
Remix v2の後継にあたるReact Routerのフレームワークモードでは、PESPAの要件がルートモジュールのactionとFormに対応します。
actionとFormが担う基準線
React Router公式ドキュメントのサーバーアクション例です。実行にはルート登録、自動生成されるRoute型、プロジェクト側で用意するfakeDbの実装が必要で、この断片だけでは動きません。
// route('/projects/:projectId', './project.tsx')
import type { Route } from "./+types/project";
import { Form } from "react-router";
import { fakeDb } from "../db";
export async function action({ request }: Route.ActionArgs) {
let formData = await request.formData();
let title = formData.get("title");
let project = await fakeDb.updateProject({ title });
return project;
}
export default function Project({ actionData }: Route.ComponentProps) {
return (
<div>
<h1>Project</h1>
<Form method="post">
<input type="text" name="title" />
<button type="submit">Submit</button>
</Form>
{actionData ? <p>{actionData.title} updated</p> : null}
</div>
);
}
actionが受け取るのはrequestで、そこからformData()を読んでいます。これはブラウザがHTMLフォームを素で送ったときのリクエスト形式そのもので、専用のJSON APIを別に用意していません。Formは、JavaScriptが動いていれば送信を横取りして文書全体を再読み込みせずに遷移を処理し、動いていなければ通常のform要素としてPOSTします。1つの受け口が両方を処理する構造が、第一要件の保証になっています。遷移中の表示はuseNavigation、画面遷移を伴わない部分更新はuseFetcherが受け持ちます。loaderとclientLoaderの使い分けはReact Routerのaction・loaderの使い方|clientAction/clientLoaderの違いとv8対応にまとめました。
ssr:falseによるサーバー処理の制約
同じフレームワークでも、設定ひとつでPESPAの条件を外れます。SPAモードの設定は次のとおりです。
// react-router.config.ts
import type { Config } from "@react-router/dev/config";
export default {
ssr: false,
} satisfies Config;
SPAモードのドキュメントによると、この設定は実行時のサーバー描画を止め、ビルド時にindex.htmlを生成します。ルートルート以外には(事前レンダリングするページを除き)loaderを置けません。サーバー側でリクエストを受ける層が無いため、JavaScriptが動かない環境ではフォーム送信が成立しません。ssr: falseはPESPAの設定項目ではなく、PESPAをやめる設定です。社内管理画面では合理的な選択ですが、その判断をした時点で本記事の設計は対象外になります。具体的な制約はRemix SPAモードとは?ssr:falseの設定と制約、React Router移行の判断で整理しています。
v8の要件と8.4.0までの変更
React Routerは2026年6月17日にv8.0.0を出し、CHANGELOGで年1回のメジャーリリースに移ると表明しました。Node 22の保守終了に合わせ、v9は2027年5月頃の見込みです。
| 項目 | 内容 |
|---|---|
| 8.0.0公開日 | 2026年6月17日 |
| Node | 22.22.0以上 |
| React | 19.2.7以上 |
| Vite | 7以上 |
| モジュール形式 | ESM専用 |
| 削除 | react-router-dom、metaのdata |
| 最新版(10月12日時点) | 8.4.0(9月15日) |
| v7系の最新 | 7.18.4(9月15日) |
効いてくるのはNodeとReactの下限です。React 18で止まっているプロジェクトは、React本体の更新が先行タスクになります。react-router-domからのインポートをreact-routerとreact-router/domへ書き換えていなければ、v8でビルドが通りません。8.4.0では内部のコンテキストが4つに分割され、useLocationだけを使うコンポーネントはnavigation.stateの変化で再描画されなくなりました。useNavigationで送信中の表示を出す画面と、それ以外の画面の再描画が切り離された形です。
Remix v2からの段階的移行とv7の公式ガイド
Remix v2からの移行ガイドは、v8のドキュメントには含まれていません。reactrouter.com/upgrading/remixはv7(7.18.4)のドキュメントへ転送され、そこに「React Router v7 is the next major version of Remix after v2」と書かれています。手順の大半はコミュニティ製のcodemod(npx codemod remix/2/react-router/upgrade)で自動化でき、Remix v2のfuture flagsをすべて有効にしていれば、主な作業は依存関係の更新で済むと案内されています。
実務の順番は、Remix v2でfuture flagsを有効化、v7へ移行、v7のfuture flagsを有効化、v8へ更新、の4段階です。v8_middlewareなどはv8で既定の挙動になっているため、v7の段階でフラグを有効にしておくとv8の更新は小さく済みます。
remix.runが今指しているのはRemix 3です。npmのremixパッケージは2026年10月1日に3.0.0がlatestになりました。依存パッケージ48件はすべて@remix-run/で始まる自前のもので、reactは含まれず、Node 24.3.0以上を要求します。Remix v2のアプリは@remix-run/reactなどのスコープ付きパッケージ(最新2.17.5)に依存しているので自動で切り替わることはありませんが、npm i remixで新しく入れると3.0.0が入ります。コンポーネントの書き方はRemix 3のコンポーネントとは?3.0正式版のhandle・mixの書き方と@remix-run/uiで扱っています。
HTML標準のform・aによる基本動作
React Routerが専用のFormコンポーネントで送信を横取りするのに対し、Remix 3は素のHTMLを書きます。サーバー側はform()で、同じURLにGET(フォーム表示)とPOST(送信処理)の2ルートを宣言します。次のコードはremix 3.0.0とNode 26.5.0で実行し、POSTに対して303とLocation: /contactが返り、続くGETで送信内容が一覧に出ることを確認したものです。
import { createRouter } from 'remix/router'
import { route, form } from 'remix/routes'
import { formData } from 'remix/middleware/form-data'
import { createHtmlResponse } from 'remix/response/html'
import { createRedirectResponse } from 'remix/response/redirect'
let routes = route({ contact: form('contact') })
let messages: string[] = []
// 投稿内容をHTMLへ埋め込む前にエスケープする(保存型XSS対策)
function escapeHtml(s: string) {
return s.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>')
}
let router = createRouter({ middleware: [formData()] })
router.map(routes.contact, {
actions: {
// GET /contact:フォームと送信済みの一覧を返す
index() {
let items = messages.map((m) => `<li>${escapeHtml(m)}</li>`).join('')
return createHtmlResponse(`<!doctype html>
<form method="POST" action="${routes.contact.action.href()}">
<input name="message" required>
<button type="submit">送信</button>
</form>
<ul>${items}</ul>`)
},
// POST /contact:保存して303でGETへ戻す
action({ get }) {
let message = String(get(FormData).get('message') ?? '')
messages.push(message)
return createRedirectResponse(routes.contact.index.href(), 303)
},
},
})
このコードはMPAのルート処理だけを示しています。ブラウザで試すにはHTTPサーバーへの接続が必要で、messagesはプロセス内の配列なので再起動すると消えます。この時点ではJavaScriptを1行も送っていないので、ただのMPAです。remix/componentのランタイムを読み込むと、componentパッケージのREADMEが「Frame Navigation」として説明する挙動が加わります。同一オリジンのリンクとフォームの遷移をNavigation APIで横取りし、HTMLを取得して既存の文書をその場で更新する、というものです。READMEは、ランタイムが起動する前のフォームは普通のHTMLフォームのままだと明記しています。サーバー側のコードは変わらず、上の303もそのまま解釈されます。原典の「ブラウザの挙動を透過的に再現する」を、ブラウザ自身のAPIに担わせた設計です。
特定のフォームだけ通常の文書遷移にしたい場合は、data-rmx-document属性を付けると横取りの対象から外れます。
この横取りにはwindow.navigationとNavigateEvent.sourceElementの両方が必要で、どちらかが無いブラウザではリンクもフォームも通常の文書遷移になるとREADMEは定めています。Navigation APIは、web-featuresのデータでChrome・Edge 102、Firefox 147、Safari 26.2が対応し、2026年1月13日にBaseline(主要ブラウザで利用可能)に入りました。sourceElementはChrome 135からです。
古いSafariや更新の止まった社内端末では、Remix 3のアプリはMPAとして動きます。機能が欠けるのではなく画面全体の再読み込みになるだけなので、PESPAの基準線は保たれます。逆に言えば、Navigation API非対応の端末が多い利用者層では、Remix 3の「SPAらしさ」は体感できません。React RouterはNavigation APIを前提にしていないため、古いブラウザでもクライアント側遷移が動きます。対象端末の構成が分かっている業務システムでは、この差が選定の材料になります。
実装先の選び方:React Router v8・Remix 3・Next.js・SvelteKit
PESPAの要件を満たせるのはReact RouterとRemix 3だけではありません。Next.jsのフォームガイド(2026年10月6日更新、16.4.0)は、Server Actionsをform要素のactionに渡す形を示し、bindを使った引数渡しについて「works in both Server and Client Components and supports progressive enhancement」と記載しています。SvelteKitは+page.server.jsのフォームアクションがJavaScriptなしで動き、use:enhanceで強化する構成を公式ドキュメントで説明しています。
| 実装先 | 最新版 | 送信の受け口 | 横取りの仕組み | UIライブラリ |
|---|---|---|---|---|
| React Router v8 | 8.4.0 | ルートのaction | Formコンポーネント | React 19.2.7以上 |
| Remix 3 | 3.0.0 | form()のPOSTルート | Navigation API | remix/component |
| Next.js | 16.4.0 | Server Actions | form要素のaction属性 | React |
| SvelteKit | 3.0.1 | +page.server.jsのactions | use:enhance | Svelte |
Remix v2・Reactの資産がある場合はReact Router v8
Remix v2のコードを延長してPESPAを続けるなら、移行先はReact Router v8です。ルートモジュールのloader・action・Formがそのまま残るため、書き換えはインポートと設定に集中します。Remix 3は名前が同じでもUIライブラリからルーターまで入れ替わるので、移行ではなく作り直しになります。Reactの資産がなく、ゼロからNext.jsと比べたい場合はNext.jsとは?16系App Routerで最小アプリを動かす手順と採用判断も材料になります。
React 18やNode 20から動かせない事情があるなら、v8は要件を満たせないのでv7系(7.18.4)に留めます。v7には9月15日の更新がありますが、継続採用するなら公式の保守方針とセキュリティ更新の提供状況を確認します。
Remix 3を選ぶ条件と見送る場面
Remix 3が向くのは、Reactへの依存を減らしたい新規案件で、サーバーがHTMLを返すMPAの書き方を基本にしたいチームです。逆に、Reactのコンポーネント資産やReact向けUIライブラリを使いたい案件、Navigation API非対応のブラウザで動的な操作感が必要な案件、Node 24.3.0以上を本番に用意できない環境では見送ります。3.0.0は公開から2週間足らずで、周辺の情報はまだ少ないのが現状です。業務システムの基盤に据えるなら、まず小さな新規案件で運用上の癖を確かめてからにします。
View TransitionsによるMPAの適用範囲
原典が2022年に挙げたMPAの弱点は、全画面の再読み込みによるフォーカス管理の難しさと、ページ遷移アニメーションが作れないことでした。原典自身も、ページ遷移のAPIが整えばMPAの適用範囲は広がると書いています。その後、文書間のView TransitionsはChrome 126とSafari 18.2で使えるようになりましたが、Firefoxは未対応で、2026年10月時点のweb-featuresではBaselineに入っていません。遷移アニメーションが要件に入るなら、まだPESPA側のフレームワークで組むほうが確実です。表示中心で更新操作が少ないサイトは、MPAにView Transitionsを足す構成も候補に入ります。
どの構成でもHTMLを返すサーバーと、送信を受ける業務ロジックの設計が先に決まります。要件定義からフロントエンドの方式選定まで含めて相談したい場合は、業務用・Webアプリ開発の支援内容を参照してください。
よくある質問
PESPAは何の略ですか?
Progressively Enhanced Single Page Appsの頭字語で、PEはプログレッシブエンハンスメントを適用した状態を指します。日本語で「pespa」と検索すると、同じ綴りのオフィス家具ブランドやスクーターの情報が上位に並びます。技術情報を探すときは「PESPA Remix」「PESPA SPA MPA」のように語を足すと目的の資料に届きます。
PEMPAとPESPAの違いは何ですか?
PEMPA(Progressively Enhanced Multi-Page Apps)は、サーバーで描画するMPAにJavaScriptの部分更新を後付けした構成で、同じ表示ロジックをサーバーとクライアントで二重に書きます。PESPAは1つのUIコードをサーバーとクライアントの両方で実行し、この二重化をなくします。原典はPEMPAを、PESPAが解消した問題の例として挙げています。
SPAとMPAのメリット・デメリットは何ですか?
MPAは画面ごとにサーバーがHTMLを返すため、初回表示と検索エンジンへの対応に強く、全画面の再読み込みで操作感が途切れるのが弱点です。SPAは画面遷移が滑らかな一方、初回に大きなJavaScriptを読み込み、履歴管理や再取得といったブラウザ機能を自前で作る負担が生じます。PESPAは両者の二択を解消する設計です。社内管理画面のように環境を統制できる用途では、初回表示時間や送信失敗時の復旧を確かめたうえでSPAを選ぶ判断もあります。
ReactだけでPESPAは作れますか?
作れません。Reactはコンポーネントを描画するライブラリで、ルーティングもサーバー側の送信の受け口も持たないためです。JavaScriptが動かない状態でフォーム送信を成立させるには、form要素がPOSTする先をサーバーに用意し、同じコードでクライアント側の遷移も処理する層が要ります。React Routerのフレームワークモードや、Next.jsのServer Actionsがその層にあたります。
Remixは今も使えますか?
使えますが、どのRemixかを確認する必要があります。npmのremixは2026年10月1日から3.0.0がlatestで、Reactに依存しないRemix 3です。Remix v2の各パッケージ(@remix-run/reactなど)は2.17.5が最新で、v2の後継はReact Router v7以降です。