古典的Webページ
サーバサイドアプリケーションでHTMLを生成し、CSS/JSを付けて返していた時代。
JavaScriptはアニメーション、ダイアログ表示などごく限られた動作を行うのみであり、あくまでコンテンツはHTMLファイルのみに存在していた。
コンテンツの更新には必ずページ遷移を必要とし、ページ全体を再読み込みする必要があった。
1990年代後半: RIAの登場
Webの普及にともない、よりリッチなアプリケーションをブラウザ上で実現したいという要求が生まれてきた。
これらはJava Applet、Shockwave、Flashなど独自のランタイムを持ち、HTML/CSS/JSのようなブラウザ機能とは別に作られ、Webページ上に埋め込まれるアプリケーションとして実現された(Rich Internet Application = RIA)。これらの動作には専用のブラウザプラグインを必要とし、ブラウザ上で動いているように見えても、実体としては別のプログラムであった。
2000年代後半: Ajaxの登場
Ajax(Asynchronous JavaScript and XML)が提唱され、各ブラウザがAPI(XMLHttpRequest = XHR)を実装したことで、JavaScriptが動的にデータを取得し、画面に反映できるようになった。
これによりページ遷移を行うことなくデータの更新が可能となり、
- 画面操作に応じたシームレスなデータ更新
- 広告のように、部分的に別途読み込まれるコンテンツ
のような動的なUIをWebブラウザ機能のみで実現することができるようになった。
DOMの仕様整備と合わさり、Webサイトは単なる「ページ」ではなく、JavaScriptによって動的に書き換えることのできる「アプリケーション」としての性質を持つこととなった。
このあたりでWeb2.0という概念が登場し始める。
情報の「送信者」と「受信者」が固定化されていた従来のWebサイトと比べ、Ajaxなどを利用して誰もが情報の発信者となれるようになったことを指す
2010年代1st quarter: SPAの普及
2007年、AppleがiPhoneでFlashをサポートしないと表明したことを皮切りに、RIAは急速に失速し消えていった。
一方でjQuery(2006年)の登場、HTML5/CSS3の普及などによりRIAで実現されていた機能がブラウザ機能だけで実現可能となり、徐々に入れ替わっていった。
これらはHTMLファイル上にはほぼコンテンツがなく、JavaScriptがほぼすべてのDOMを構築する「アプリケーション」であった。ページ遷移を行わないことからSingle Page Application(SPA)と呼ばれ、対して従来型のページ遷移を必要とするものをMulti Page Application(MPA)と呼ぶようになる。
従来の文書(ページ)としての役割から、アプリケーションとしての役割がメインになってきた
SPAを作ることを前提としたJavaScriptフレームワークも作られ、
- Backbone.js
- AngularJS (初代)
などが使われた。
またこの頃Node.js(2009年)が登場する。サーバ上でJavaScriptを動かすものとして登場したが、当時のJavaScript仕様にないモジュールシステムを備えていたこともあり、JavaScriptアプリケーションの開発環境としても広まることになる。これ以降、JavaScriptコードには事前ビルドの工程が挟まることが当たり前になっていき、エコシステムが急速に複雑化する。
2010年代2nd quarter: Reactの登場
2013年にSPAフレームワークとしてReact(当時はReact.js)が登場する。
(なお当時はフレームワークではなく、UIライブラリという立ち位置)
最大の特徴は
- 宣言的UI
- コンポーネント指向
を実用的なレベルで実現していたことで、これにより圧倒的な開発効率を実現した。コンポーネントベースの開発もReactにより急速に普及した。
それまで存在していたフレームワークもReactの影響を強く受け、
- React
- Vue.js (2以降)
- Angular (2以降)
が主要なフレームワークとして台頭する事になる。
また同時期にブラウザでHistory APIなどが整備され、ブラウザの戻る・進む機能、そしてURLの書き換えをJavaScriptから行えるようになった。これはページ遷移を(実際には発生させずに)JavaScriptでエミュレートできるようになったことを意味する。
これらにより、従来のMPAをもSPAで実現できるようになり、
-
開発効率の向上
- Reactおよび以降のフレームワークでの開発効率がそもそも画期的
- 従来はサーバで構築されるHTMLと、クライアントで動作するJavaScriptという別々に動作する2つのものがありデバッグ・テストが困難だったのが、JavaScript1つで済む
-
より最適化されたUIの実現
- クライアントサイドページ遷移
- 遅延レンダリング
- …etc
といった利点を享受するべく、MPAをSPAで実装することが試みられるようになる。
またTypeScript(2012年)の登場によりJavaScript開発に型安全性がもたらされ、これも開発効率の向上に大きく貢献することになる。
2010年代後半: MPAの代替
SSRの普及
MPAをSPAで実装するうえでは、主に次の2点が従来型MPAに比べた不利要素であった。
-
JavaScriptロード完了までページの表示ができない
- 特に初回アクセス時には大きなJavaScriptファイルのダウンロードが走り、これのロードが完了するまでは真っ白 = UX上不利
- 当時のクローラはJavaScriptを解釈できなかったため、検索エンジンのクローラには空ページと認識される = SEO上致命的
-
表示に必要なデータ取得用のAPIをすべて公開する必要がある
- 秘密情報を含むなど、公開したくない場合に対応できない
- 攻撃を受けやすくなる
このため、「アクセス時にサーバサイドで一度JavaScriptを実行させてHTMLを出力しておく」という手法が取られるようになった。これがServer Side Rendering = SSR である。
SSRは以下のような手順で実行される。
- ブラウザからアクセスされたら、サーバ側で必要なデータを取得し、JSONにする
- JavaScriptにJSONデータを与えて実行し、HTMLを生成する
- HTML + JavaScript + JSONのセットをブラウザに返す
- ブラウザはまず受け取ったHTMLを即描画する
-
続いてJavaScriptにJSONを与えて再度実行する(Hydration)
- これは変数の状態を再現するため
同じJavaScriptを2回実行するため無駄はあるものの、初期HTMLが存在するためSPAの問題を回避できるようになった。
初期のSSRは開発者が独自に実装する形であったが、次第に専用のSSRフレームワークが登場し、
- Next.js (React)
- Nuxt.js (Vue.js)
などが主流となる。
またSSRの派生として、アクセス時ではなくビルド時にHTMLを生成するStatic Site Generation(SSG)なども登場することとなった。
SSRの登場により、従来のSPAで行っていたブラウザ側でのデータ取得・描画をClient Side Rendering(CSR)と呼んで区別するようになった。CSRが完全に廃れたわけではなく、SSRアプリケーションでも部分的にCSRを併用する場合もある。
SPAベースではないフレームワークの登場
当初のSPAは文字通り「アプリケーション」であり、画面全体をJavaScriptが制御していることに必然性があった。
一方でMPAの代替として使うようになると、SPAフレームワークは機能過多となってきた。ページの大半は静的で、動的コンテンツは一部にしかないからである。SPAフレームワークはファイルサイズも大きいため、表示速度にも直結する問題となっていた。
そこでSPAフレームワークと同等の書き心地を保ちつつ、 ビルド時の最適化 によりページごとの静的HTML + 少しのJavaScriptを出力するようなフレームワークが登場し始めた。
代表的なものは
- Svelte
- SolidJS
であり、のちにこれらを使ったSSRフレームワークも登場することとなる。
またデフォルトでJavaScript系機能を一切持たず、必要に応じて他のフレームワークを導入するスタイルであるAstroなどのようなものも登場した。
2020年代: 先鋭化 VS 小規模化
Core Web Vitalsの登場とSSRの課題
2020年にWebサイトのパフォーマンス指標として、Googleが Core Web Vitals(CWV) を発表した。
これはざっくりいうと 表示できるようになったものからさっさと表示しろ というものであり、従来のページ表示速度の概念を覆すものだった。
2021年よりGoogleでの検索結果スコアにCWVが使われることになったため、これ以降はいかにCWVを改善するかを皆が考えることになる。
CWVを考えた場合に、SSRには以下の問題があった。
-
遅延レンダリングができない
- ページ全体をレンダリングし終えないと、レスポンスを返せない
- CSRではデータ取得が完了した部分からレンダリングできていたので、先祖返りしてしまった
- 部分的にCSRを採用すれば解決するものの、実装を大きく変える必要がある
-
JavaScriptが無駄に重い
- サーバ・クライアント両方で同じ処理を実行する関係上、同じJavaScriptをクライアント側に送信する必要がある
- 一度レンダリングしたら使わないような処理分も送らなければならない
ここで各フレームワークの対策が分かれることになる。
React/Next.js: SPAとサーバ機能の連携
React / Next.jsは機能を複雑化させ、サーバとの連携機能を強化していく方向に動いた。
-
Server Component
- コンポーネントをサーバ側でレンダリングし、JSONにシリアライズして返す(SSRとは別)
- ブラウザ側に送信するJavaScriptを減らすことにつながる
-
SSR Streaming
- SSRで遅延レンダリングを実現する
- HTTP chunkの機能を利用し、遅れて描画する部分だけ後から追加送信する
-
Partial Prerendering
- 静的部分をあらかじめ生成しておき、それ以外の部分をSSR Streamingにより遅延送信することで高速化を狙う
React向けSSRフレームワークであったRemixは Preactへの移行を表明 している
その他フレームワーク: ビルド時最適化への移行
それ以外のフレームワークはSvelte・SolidJSのようにビルド時最適化を活用し、そもそものJavaScriptを削減し、ページ全体を軽量化する方向に動いている。
仮想DOMを使用するSPAであるVue.jsもビルド時最適化を活用した「vapor mode」を実装する方向に動いており、次期バージョンのVue 3.6でExperimental機能としてリリース予定である。
軽量フレームワークの登場
上記のようなフレームワークが進化する一方で、これらフレームワークは事前にビルドのステップを要し、セットアップが複雑であった。jQuery程度の機能で良かったユーザはずっと置き去りになっていた。
これらユーザを取り込むべく、JSファイルを1つ読み込み、HTMLへカスタム属性を付与するだけで使える軽量フレームワークも登場している。
以下はAlpine.jsの例。
<script src="//unpkg.com/alpinejs" defer></script>
<div x-data="{ open: false }">
<button @click="open = true">Expand</button>
<span x-show="open">
Content...
</span>
</div>
というわけで歴史を元に、今の開発についてふれる。