SPA
- なぜ生まれた?
- ページを移るたびにサーバーが画面を組み直し、アプリのような途切れない操作にならなかった。
- 何を解決した?
- 最初に読み込んだ1枚のページを残し、画面の切り替えもデータの取得も JavaScript が受け持った。
- 何を残した?
- URL・戻るボタン・検索エンジンへの対応と、JavaScript を待つ初期表示の遅さが課題になった。
- 登場
- 状態
- 現役
SPA という考え方が無かったころ
Section titled “SPA という考え方が無かったころ”リンクをたどるたびに、サーバーが画面を組み直す
Section titled “リンクをたどるたびに、サーバーが画面を組み直す”Web のアプリケーションは、リンクやボタンを押すとサーバーが新しいページを返す形で作られていました。 2003年、Netscape の技術エバンジェリストたちは、この形を “this model has begun to show its age and its limitations”(このモデルは古さと限界を見せはじめている)と書いています。
例に挙がったのは Web メールで、メールが多いと「次のページ」を押すたびに新しいページを待つことになり、回線が遅いといらだたしい操作でした。 サーバーの側についても、“the server often has to rebuild all of the context of the user interface with each new request”(新しい要求のたびに、画面の文脈をすべて組み直さなければならないことが多い)と書いています。 同じ文書は、この作り方が便利で実装しやすく、だからいまも広く使われている、とも認めていました。
1枚ずつしか扱えない wiki
Section titled “1枚ずつしか扱えない wiki”開発者の Jeremy Ruston は、慣れた人ほど複数の wiki のページをブラウザのタブで同時に開き、見比べたり文を写したりしていることに気づいていました。2025年の回想で、本人はこう振り返っています。
“standard wiki user interfaces have always been designed exclusively for the presentation and manipulation of single pages at once.” (ふつうの wiki の画面は、いつも1度に1ページだけを見せて操作するように作られていた)
考えがまとまったのは2004年4月、Gmail が1通ずつのメールを会話のまとまりに組み替えて見せているのを見たときでした(回想なので、2005年に付いた Ajax という語で書いています)。Gmail と Ajax の作り方そのものは、Ajax の記事で扱います。
SPA という考え方
Section titled “SPA という考え方”「すべてのナビゲーションを1枚のページの中で」
Section titled “「すべてのナビゲーションを1枚のページの中で」”2003年5月16日、Netscape の開発者向けサイト DevEdge に「Inner-Browsing: Extending Web Browsing the Navigation Paradigm」が載りました。 著者は Netscape の Marcio Galli と Ian Oeschger、Intelinet.com.br の Roger Soares です。Inner-Browsing は著者たちが付けた名前でした。
“a model in which all navigation occurs within a single page, as in a typical application interface” (ふつうのアプリケーションの画面のように、すべてのナビゲーションが1枚のページの中で起きるモデル)
要求がサーバーに行っても、開いているページは置き換えられずに書き換えられる、という説明です。例には、入力中に綴りを確かめる機能、一覧と本文をひとつながりに見せる Web メール、株価のティッカーを挙げています。 データを取る手段には、隠した iframe と、“currently offered in Netscape 7 and Internet Explorer 6”(2003年の時点で Netscape 7 と Internet Explorer 6 にある)XMLHttpRequest を挙げました。どちらも、2005年に Ajax と名付けられる作り方の部品です。
なぜページの一部を書き換えるだけでなく、1枚のページにまでしたのか
Section titled “なぜページの一部を書き換えるだけでなく、1枚のページにまでしたのか”著者たちが目指したのは、一部を速く書き換えることだけではありません。“the page is the browsing device during the experience”(使っているあいだ、ページが閲覧の装置になる)と書き、ページを離れないのでそれまでの文脈が保たれるとしています。 目標は、連絡先の管理ソフトのような Web 以外のアプリケーションに近い動きでした。ページを、文書ではなくアプリケーションそのものにする考え方です。
8年後の2011年、Twitter の Ben Cherry も、twitter.com はもう Web のページの集まりではなく、ブラウザで開くと起動する “simply an application”(ただのアプリケーション)だと書き、アプリケーションならふつうの Web サイトより速く途切れない体験を出せるとしました。
「SPA」という名前はいつ、誰が言いはじめたのか
Section titled “「SPA」という名前はいつ、誰が言いはじめたのか”この記事は、Inner-Browsing の公開日の2003年5月16日を登場としています。すべてのナビゲーションを1枚のページで行う考え方に名前を付けて書いた文書のうち、一次資料で日付まで確かめられたもっとも早いものだからです。 ただし Inner-Browsing は “single page” とは書いても、“single-page application” という語は使っていません。
2006年4月、オランダの研究所 CWI の報告 SEN-R0603(Ali Mesbah、Backbase 社の Kees Broenink、Arie van Deursen)は、Ajax の中心にあるものを “a single page interface (SPI) model” と呼び、ページを順にたどる従来の multi page interface(MPI)と対比しました。 SPI では “changes are made to individual components that compose the interface, as opposed to refreshing the entire page”(ページ全体を読み直すのではなく、画面を作る個々の部品が書き換えられる)とし、このモデルに基づくものを Single Page Internet Application(SPIA)と定義しています。
「single-page application」という語の初出は、一次資料では確かめられませんでした。2005年に Steve Yen が使ったという説が広く書かれていますが、元の文書は見つけられていません。 考え方は2003年に名前付きで書かれ、「single page」を冠した呼び方が論文で定義されたのは2006年です。 1枚のページで完結するものは、「single page」の呼び方が定まる前から作られていました。
SPA を実装した技術
Section titled “SPA を実装した技術”1つの HTML ファイルで完結した TiddlyWiki
Section titled “1つの HTML ファイルで完結した TiddlyWiki”Gmail に刺激を受けた Ruston は、ほとんど経験の無かった HTML と JavaScript で試作を始めます。 2004年9月(本人の回想では20日)に出した最初の版は、“a simple, self-contained static 48KB HTML file”(単純で自己完結した、48KB の静的な HTML ファイル)でした。 2004年9月23日の時点で Wayback Machine に残る版では、タイトルと本文を持つ「tiddler」という単位を、JavaScript が同じページの中の要素として組み立てて表示しています。回想に、SPA という語は出てきません。
画面を組み立てる仕事が、ブラウザに移る
Section titled “画面を組み立てる仕事が、ブラウザに移る”2010年9月、Twitter は #NewTwitter を公開しました。2012年の公式ブログによれば、このときの作りは “pushed all of the UI rendering and logic to JavaScript running on our users’ browsers”(画面の描画と処理をすべて、利用者のブラウザで動く JavaScript に任せた)もので、モバイルのアプリと同じように REST API を直接呼んでいました。
画面の大部分を JavaScript で組み立てると、そのコードを整理する道具が要ります。 2010年10月13日に Backbone.js 0.1.0、10月20日に AngularJS 0.9.0 が公開され、2011年ごろには Ember.js も出てきます(確かめられた最古の版は2011年12月14日の 0.9.1)。のちの React も含め、それぞれの誕生は別の記事で扱います。
URL・戻るボタン・検索エンジンはどう扱われたのか
Section titled “URL・戻るボタン・検索エンジンはどう扱われたのか”1枚のページの中で画面を切り替えると、URL が変わりません。ブックマークも共有も戻るボタンも、URL を頼りにしています。
2006年の CWI の報告は、アプリケーションの状態へのリンクには URI のフラグメント(# より後ろ)を使えると書いていました。2011年の Cherry も、当時のブラウザでは URL のハッシュを使わないかぎり、この種のアプリケーションでふつうの URL の構造を支えられないと書いています。
ただし、2009年10月7日の Google の提案が書くとおり、“URL fragments are never sent with requests to servers”(URL のフラグメントは、要求と一緒にサーバーへ送られない)。サーバーも検索エンジンのクローラーも、# より後ろを受け取れません。
Google は、状態を表すフラグメントに目印の ! を付けて #!state とし、クローラーは ?_escaped_fragment_=state で問い合わせ、サーバーはヘッドレスブラウザで作った HTML のスナップショットを返す、という仕組みを提案しました。
2011年の twitter.com も、投稿を twitter.com/#!/〈ユーザー名〉/status/〈番号〉 の形の URL で指していました。
ハッシュに頼らない道は、仕様の側で用意されていきます。2009年8月25日の W3C の HTML5 の草案には、ページを読み直さずに履歴の項目と URL を足す pushState(data, title, url) が載りました(2014年10月28日の勧告にも入っています)。
2010年12月7日、GitHub の Chris Wanstrath は、ファイルの一覧を横に滑らせて切り替える Tree Slider を紹介しています。
“Basically we intercept your click, call pushState() to change the browser’s URL, load in data with Ajax, then slide over to it.” (基本的には、クリックを横取りし、pushState() でブラウザの URL を変え、Ajax でデータを読み込み、そこへ滑らせる)
戻るボタンでは onpopstate が呼ばれて元に戻せること、使えるのは2010年12月の時点で最近の Safari と Chrome、Firefox 4 であること、Facebook と Flickr が先に使っていたことも書いています。
2011年2月の Cherry によれば、Twitter も pushState を組み込み済みでしたが、ブラウザの不具合のために #NewTwitter では見送りました。
コードで見る違い
Section titled “コードで見る違い”/users/42 の画面を開く処理で比べます。どちらもこの記事のための作例で、load() はデータを取って画面を書き換える関数として省いています。使っているのは、2009年8月の W3C の草案にある hashchange・pushState()・popstate の範囲です。
// /#!/users/42 のような URLfunction route() { var path = location.hash.replace(/^#!/, ''); // "/users/42" load(path);}window.onhashchange = route; // # より後ろが変わったときroute(); // 最初に開いたとき// /users/42 のような、ふつうの URLdocument.onclick = function (e) { var a = e.target; if (a.tagName != 'A') return; e.preventDefault(); // ページの読み直しを止める history.pushState(null, '', a.getAttribute('href')); load(location.pathname);};window.onpopstate = function () { // 戻る・進むとき load(location.pathname);};#! の形では、/users/42 はブラウザの中の JavaScript にしか見えません。
pushState の形なら、同じ URL を直接開いたとき、/users/42 がサーバーに届きます。 サーバーがその画面の HTML を返せば、JavaScript が動かない環境でも中身を読めます。
残した課題と、次に来たもの
Section titled “残した課題と、次に来たもの”SPA は本当に速かったのか
Section titled “SPA は本当に速かったのか”2012年5月29日、Twitter の Web Core チームの Dan Webb は、描画をサーバーに戻したと書きました。2010年の作りは “it lacked support for various optimizations available only on the server”(サーバーでしかできないさまざまな最適化を使えなかった)という自己評価です。 Twitter は、ページを開いてから最初のツイートが見えるまでの時間を利用者の一部で計り、JavaScript の解析と実行が大きく足を引っぱっていることを見つけました。
“In our fully client-side architecture, you don’t see anything until our JavaScript is downloaded and executed.” (すべてをクライアントで行う作りでは、JavaScript をダウンロードして実行し終えるまで何も見えない)
| 2010年9月の #NewTwitter | 2012年5月の作り直し | |
|---|---|---|
| 最初の画面を描く場所 | 利用者のブラウザ(JavaScript) | Twitter のサーバー |
| 投稿のページの URL | #! の付いた URL | #! を外した URL |
| 最初のツイートが見えるまで | JavaScript の実行を待つ | 2010年の作りの5分の1(Twitter の計測) |
Twitter は、hashbang の制約よりも、初期表示の速さを戻した主な理由に挙げています。 サーバーで描いたページの上で JavaScript のアプリケーションを非同期に立ち上げる形にし、History API による部分的な読み込みも予定していると書きました。 ページの中の切り替えでは出やすい速さが、最初に開くときには JavaScript を待つぶん失われやすい、というトレードオフです。数字は Twitter 自身の計測で、ほかのサイトにそのまま当てはまるわけではありません。
検索エンジンが JavaScript を描画するようになる
Section titled “検索エンジンが JavaScript を描画するようになる”2015年10月14日、Google は2009年の AJAX クロールの仕組みを勧めるのをやめました。JavaScript や CSS のファイルを読むのを妨げなければ、2015年10月の時点で “we are generally able to render and understand your web pages like modern browsers”(おおむね、現代のブラウザと同じようにページを描画して理解できる)という説明です。
代わりに、JavaScript が無くても中身が読める作りから積み上げる progressive enhancement を勧め、例に History API の pushState() を挙げました。
最初の HTML を、もう一度サーバーで作る
Section titled “最初の HTML を、もう一度サーバーで作る”最初の HTML をサーバーで作り、その上で JavaScript のアプリケーションを動かすという Twitter の2012年の形は、その後、描画をどこで行うかの選び方(CSR・SSR・SSG)や、サーバーで作った HTML に JavaScript をつなぎ直すハイドレーションとして整理されていきます。それぞれ別の記事で扱います。
2026年10月時点の立ち位置
Section titled “2026年10月時点の立ち位置”SPA として作られたサイトがどれだけあるかを直接示す数字は、見つけられませんでした。 近い数字を、何の割合かを添えて並べます。
| 指標 | 数字 | 出典 |
|---|---|---|
| この1年に React で本格的な開発をした回答者(全回答者) | 44.7% | Stack Overflow Developer Survey 2025 |
| 同じく Next.js | 20.8% | 同上 |
| 同じく Angular | 18.2% | 同上 |
| 同じく Vue.js | 17.6% | 同上 |
| React が見つかったページ(デスクトップ・モバイルとも) | 10%(前年 8%) | Web Almanac 2024 |
| モバイルの中央値のホームページの JavaScript | 632 KB(ページ全体は 2.6 MB) | Web Almanac 2025 |
Stack Overflow の数字はフレームワークで開発した人の割合で、どれも SPA だけを作る道具ではありません。
HTTP Archive の Web Almanac 2025 の Performance の章は、“Modern single page websites often use JavaScript-based navigations, where content changes without a full page reload.”(現代の single page のサイトは、ページ全体を読み直さずに中身を変える、JavaScript によるナビゲーションをよく使う)と書き、そうした切り替えは2025年時点の Web Vitals の計測で捉えきれないことがあるとしています。 同じ年の Page weight の章は、AI のクローラーは JavaScript を描画しないので、大事な情報は最初の HTML に入れておくべきだと書いています。 1枚のページの中で切り替えるサイトは計測の章で前提として扱われる一方、JavaScript を実行しない読み手にどう中身を届けるかが、2009年の検索エンジンのときと同じように、ふたたび書かれています。
- Web のアプリケーションは、ページを移るたびにサーバーが画面を組み直して返す形でした。2003年に Netscape の技術者はその限界を書き、Ruston は1ページずつしか扱えない wiki に不満を持っていました。
- 2003年5月16日の Inner-Browsing が、すべてのナビゲーションを1枚のページの中で行う考え方を名前付きで示し、2006年の CWI の報告が single page interface を定義しました。「single-page application」の語の初出は確かめられていません。
- 「single page」の呼び方が定まる前の2004年に TiddlyWiki があり、2010年には Twitter が描画をブラウザに任せ、Backbone.js や AngularJS が続きました。
- URL の問題は
#!と Google の AJAX クロールで補われ、のちに pushState に移りました。初期表示の遅さから Twitter は2012年に描画をサーバーに戻し、CSR・SSR・ハイドレーションの整理につながりました。 - 2026年10月時点で SPA の割合を示す数字はありませんが、Web Almanac 2025 は single page のサイトの計測の課題と、JavaScript を描画しないクローラーへの注意を書いています。
年表
Netscape DevEdge が「Inner-Browsing」を公開し、すべてのナビゲーションを1枚のページの中で行う作り方を示す web.archive.org
Jeremy Ruston が、48KB の1つの HTML ファイルで動く TiddlyWiki の最初の版を公開する(本人の回想による日付) tiddlywiki.com
CWI の報告 SEN-R0603 が、Ajax の中心にあるモデルを single page interface(SPI)と呼び、Single Page Internet Application を定義する ir.cwi.nl
W3C の HTML5 の Working Draft に、ページを読み直さずに履歴と URL を足す `pushState()` が載る w3.org
Google が、`#!` の付いた URL を検索エンジンが読めるようにする AJAX クロールの仕組みを提案する web.archive.org
GitHub が、pushState で URL を変えながらファイルの一覧を切り替える Tree Slider を紹介する github.blog
Twitter の Ben Cherry が、twitter.com の `#!` の URL を擁護する「Thoughts on the Hashbang」を書く adequatelygood.com
Twitter が、描画をサーバーに戻して `#!` を外し、初期表示の時間を5分の1にしたと発表する web.archive.org
Google が、2009年の AJAX クロールの仕組みを勧めるのをやめる web.archive.org
系譜
この技術が出てくる物語
出典
- 一次Inner-Browsing: Extending Web Browsing the Navigation Paradigm(Marcio Galli・Roger Soares・Ian Oeschger, Netscape DevEdge, 2003-05-16) devedge.netscape.com 保存版
- 一次The Story of TiddlyWiki(Jeremy Ruston, 2025-07-30) tiddlywiki.com
- 一次TiddlyWiki(tiddlywiki.com、2004年9月23日時点) tiddlywiki.com 保存版
- 一次SPIAR: An architectural style for single page internet applications(A. Mesbah・K. Broenink・A. van Deursen, CWI Report SEN-R0603, 2006年4月) ir.cwi.nl
- 一次HTML 5 6.10 Session history and navigation(W3C Working Draft, 2009-08-25) w3.org
- 一次HTML5(W3C Recommendation, 2014-10-28) w3.org
- 一次A proposal for making AJAX crawlable(Official Google Webmaster Central Blog, 2009-10-07) googlewebmastercentral.blogspot.com 保存版
- 一次The Tree Slider(Chris Wanstrath, The GitHub Blog, 2010-12-07) github.blog
- 一次Thoughts on the Hashbang(Ben Cherry, 2011-02-11) adequatelygood.com
- 一次Improving performance on twitter.com(Twitter Engineering, 2012-05-29) blog.twitter.com 保存版
- 一次Deprecating our AJAX crawling scheme(Google Search Central Blog, 2015-10-14) developers.google.com 保存版
- 一次Backbone.js(変更履歴) backbonejs.org
- 一次AngularJS CHANGELOG.md raw.githubusercontent.com
- 一次Ember.js CHANGELOG.md raw.githubusercontent.com
- 二次Stack Overflow Developer Survey 2025(Technology) survey.stackoverflow.co
- 二次Web Almanac 2024 JavaScript(HTTP Archive) almanac.httparchive.org
- 二次Web Almanac 2025 Page weight(HTTP Archive) almanac.httparchive.org
- 二次Web Almanac 2025 Performance(HTTP Archive) almanac.httparchive.org