コンテンツにスキップ

ハイドレーション

なぜ生まれた?
サーバーで描いた HTML は見えても動かず、捨てて描き直せば利用者の操作が乱れた。
何を解決した?
同じコードでサーバーが HTML を描き、ブラウザはその DOM に動きをつなぎ直した。
何を残した?
表示されても押せない時間と、同じ画面を二度処理する負担が残った。
登場
状態
現役

ハイドレーションという考え方が無かったころ

Section titled “ハイドレーションという考え方が無かったころ”

画面をすべてブラウザの JavaScript で組み立てる SPA は、最初の表示が JavaScript を待つぶん遅く、検索エンジンにも中身を渡しにくいものでした。 2013年ごろ、Airbnb の Spike Brehm たちは、同じ JavaScript のコードをサーバーでも動かして最初の HTML を作る方向に進みます。描画をどこで行うかの選び方と、Airbnb がサーバーとブラウザで同じ処理を別々の言語で書いていた経緯は、CSR・SSR・SSG の記事で扱っています。 この記事が追うのは、その先の問いです。サーバーで作った HTML がブラウザに届いたあと、それをどう動かすのでしょうか。

サーバーの HTML は、そのままでは押しても動かない

Section titled “サーバーの HTML は、そのままでは押しても動かない”

2013年1月30日、Brehm は会社のエンジニアリングブログに「Our First Node.js App: Backbone on the Client and Server」を書きました。 Airbnb はモバイルの Web サイトを、Node.js の上で Backbone をサーバーでも動かす Rendr で作り直していました。同じビューのコードが、サーバーでは HTML の文字列を返し、ブラウザでは画面を書き換えます。

サーバーの HTML なら、中身はすぐに見えます。Brehm によれば、古い作りの検索結果のページは、モバイル回線で中身が見えるまでに極端な場合10秒を超えました。サーバーが HTML をまるごと返せば、ダウンロードに2秒かかっても、すぐに描けます。 ただ、届いたのはただの HTML です。ボタンを押したときの処理を動かすには、ブラウザの側にもビューのオブジェクトが要ります。やり方は2つ考えられました。

サーバーの HTML を捨てて描き直すすでにある DOM にビューを付け直す
ブラウザでの仕事ビューを作り直し、DOM をまるごと差し替えるビューを作り、HTML のどの部分のものかを対応づける
困りごと利用者が触りはじめた要素が消されて置き換わり、操作がおかしくなるおそれどの要素がどのビューのものか、ビューに要るデータは何かを、HTML から知る必要がある

Brehm は前者を “which we decided against”(選ばなかった)と書いています。

ハイドレーションという考え方

Section titled “ハイドレーションという考え方”

サーバーで作った HTML があるのに、なぜブラウザでもう一度同じ画面を描くのか

Section titled “サーバーで作った HTML があるのに、なぜブラウザでもう一度同じ画面を描くのか”

Airbnb は、サーバーが作る HTML に data-view="listings/index" のような属性を付け、どのビューの部分かを書き込みました。

“On pageload, we find all DOM elements with the data-view=“some_view_class” attribute, instantiate view objects for each, and reconstruct the view hierarchy.” (ページを読み込んだら、data-view 属性の付いた DOM の要素をすべて探し、それぞれのビューのオブジェクトを作り、ビューの親子関係を組み立て直す)

ブラウザはビューをもう一度組み立てますが、DOM は作り直しません。サーバーの HTML を捨てずに、すでにある DOM へ動きを付け直す。 のちにハイドレーションと呼ばれる考え方は、この形をしています。

投稿は限界も認めています。ビューを作るのに要るデータをすべて DOM から読み取れなければならず、“This is the pattern we came up with in order to ship”(出荷するためにひねり出したやり方だ)とし、サーバーで各ビューに固有の ID を振るほうがよいと書いています。

この記事は、この投稿の日付の2013年1月30日を登場としています。同じコードでサーバーとブラウザの両方が描き、ブラウザがサーバーの DOM を引き継ぐ形を、一次資料で確かめられた早い例だからです。

なぜ「ハイドレーション(水を戻す)」と呼ぶのか

Section titled “なぜ「ハイドレーション(水を戻す)」と呼ぶのか”

hydrate は「水を与える・水分を戻す」という意味の英語です。Web の画面についてこの語を誰が最初に使ったかは、一次資料で確かめられませんでした。確かめられた用例を並べます。

時期どこで呼び方指しているもの
2014年10月Yahoo の Fluxible の READMEdehydrate/rehydrateサーバーの状態のスナップショットを送り、ブラウザで同じ状態に戻すこと
2017年9月React 16ReactDOM.hydrateサーバーで描いた HTML に React をつなぐ API
2018年7月Vue の SSR ガイドhydrationサーバーの静的な HTML を、データの変化に反応する DOM に変えること
2019年2月Google「Rendering on the Web」(re)hydrationサーバーの HTML の DOM とデータを使い回して、ブラウザの JavaScript のビューを起動すること

Fluxible の用例は、乾かして運び、着いた先で水を戻すというたとえどおりですが、戻すのは DOM ではなくデータの入れ物(ストア)の状態でした。 2018年の Vue のガイドは、“we obviously do not want to throw that away and re-create all the DOM elements”(それを捨てて DOM の要素をすべて作り直したくはない)と書き、代わりに静的なマークアップを “hydrate” すると、語を引用符で囲んで使っています。

サーバーの HTML を引き継ぐ仕組みは2013年にはあり、React がそのための API を hydrate と名付けたのは2017年の React 16 です。

ハイドレーションを実装した技術

Section titled “ハイドレーションを実装した技術”

React が公開された2013年、Facebook の Pete Hunt は Instagram のために、サーバーで HTML を作る仕組みを React に入れていました(経緯は React の記事で扱います)。 React は、データから描いた画面を前回の描画と比べ、違うところだけを DOM に当てます。すでにある DOM を使い回すこの仕組みの上に、サーバーの HTML を引き継ぐ機能が足されていきました。

2013年7月17日の 0.4.0 は、DOM の要素を追う印を id から data-reactid 属性に変え、サーバーで HTML を作る React.renderComponentToString を整えました。 7月26日の 0.4.1 は、“Added checksums to detect and correct cases where server-side rendering markup mismatches what React expects client-side.”(サーバーで描いたマークアップが、クライアントの React の期待と食い違う場合を見つけて直すため、チェックサムを加えた)と書いています。 このころの React は、ブラウザでの最初の描画がサーバーの結果と完全に一致することを求めていました。

同じ関数が「つなぐ」か「消す」かを決めていた(2017年)

Section titled “同じ関数が「つなぐ」か「消す」かを決めていた(2017年)”

React 16 を作っていた2017年7月15日、Sebastian Markbåge は issue #10189 で問題を整理しました。 ReactDOM.render は、ブラウザだけで描く場合にもサーバーの HTML をつなぐ場合にも使われ、どちらにするかは data-reactid があるかどうかで決めていました。

例に挙がったのは読み込み中の表示です。最初の DOM に <div class="spinner">Loading...</div> があるところへ、ブラウザだけで描くつもりで render すると、React は既存の要素を使い回して属性を直さないので、<div class="spinner"><span>App</span></div> という画面ができてしまいます。 選択肢の1つが、ハイドレーション専用の API を作ることでした。

8月1日、Dan Abramov の PR #10339 がマージされます。ReactDOM.hydrate() は “never clears the existing content”(既存の中身を決して消さない)API で、render がマークアップを使い回そうとすると、非推奨の警告が出るようになりました。 2017年9月26日に公開された React 16 の告知は、こう書いています。

“It no longer requires the initial render to exactly match the result from the server. Instead, it will attempt to reuse as much of the existing DOM as possible. No more checksums!” (最初の描画がサーバーの結果と完全に一致する必要はもうない。代わりに、既存の DOM をできるだけ使い回そうとする。チェックサムはもうない)

ブラウザ側で、サーバーが描いた #app の中身に React をつなぐ処理を比べます。どれもこの記事のための作例で、JSX は事前に変換する前提です。

server.js と client.js(React 0.4.1、作例)
/** @jsx React.DOM */
// サーバー: 0.4 では HTML をコールバックで受け取る
React.renderComponentToString(<App />, function (html) {
res.end('<div id="app">' + html + '</div>'); // data-reactid と data-react-checksum 付き
});
// ブラウザ: ブラウザだけで描くときと同じ関数を呼ぶ
React.renderComponent(<App />, document.getElementById('app'));
client.js(React 16、作例)
ReactDOM.hydrate(<App />, document.getElementById('app'));
client.js(React 18、作例)
import { hydrateRoot } from 'react-dom/client';
hydrateRoot(document.getElementById('app'), <App />);

0.4.1 では、同じ renderComponent がサーバーの HTML を使うかどうかを印から判断していました。 16 からは、サーバーの HTML につなぐのか、空から描くのかを、呼ぶ関数で書き分けます。 スピナーの例のような取り違えを避けるための分け方でした。

React の外でも、2018年7月の Vue の SSR ガイドでは、サーバーが描いた根の要素に data-server-rendered="true" が付き、ブラウザ側の app.$mount('#app') はそれを見てハイドレーションのモードで動きました。

操作された部分から先に(React 18、2022年)

Section titled “操作された部分から先に(React 18、2022年)”

2022年3月29日の React 18 は hydrateRoot を加え、変更履歴は “New features in React 18 don’t work without it.”(React 18 の新しい機能は、これが無いと動かない)と書きました。 その新しい機能の1つが selective hydration です。前日の3月28日、React 18 の作業部会の解説は、ハイドレーションを主にイベントハンドラーをつなぐ仕事と位置づけ、それまでの問題をこう書いています。

“Previously hydration required all of the code to be downloaded and ran before we could respond to user interactions.” (以前は、利用者の操作に応えられるようになる前に、すべてのコードをダウンロードして実行する必要があった)

React 18 では、Suspense で区切った部分は最初はハイドレーションされず、クリックやキー入力があると、その部分を先に同期でハイドレーションします。何も操作が無ければ、空いた時間に順にハイドレーションします。

表示されているのに押せない時間があるのはなぜか

Section titled “表示されているのに押せない時間があるのはなぜか”

ハイドレーションでは、画面はサーバーの HTML ですぐに見えますが、ボタンが動くのは、ブラウザが JavaScript を読み込み、同じ画面をもう一度組み立ててイベントをつないだあとです。 2019年2月6日の Google の「Rendering on the Web」は、サーバーで描いたページは “deceptively loaded and interactive”(読み込みが終わって操作できるように見えて、実はそうでない)ことが多く、モバイルでは操作できるまで数秒から数分かかることもあると書きました。

“Ultimately, the reason comes down to User Experience: it’s extremely easy to end up leaving users in an “uncanny valley”.” (結局のところ理由は利用者の体験で、利用者を「不気味の谷」に置き去りにしてしまうのがきわめて簡単なのだ)

同じ文書は、もう1つの重さも指摘しています。ブラウザ側がデータを取り直さずにサーバーの続きから動けるよう、多くの仕組みは画面に使ったデータを <script> に埋め込みます。サーバーは、描いた画面と、そのもとのデータと、画面を作るコード一式を、そろって送ることになります。

ハイドレーションは本当に速いのか

Section titled “ハイドレーションは本当に速いのか”

何を測るかで、評価が分かれます。

Airbnb(2013年)Google(2019年、2026年1月更新)
見ていたもの中身が見えるまでの時間操作に応えられるまでの時間
評価“It feels 5x faster”(5倍速く感じる)FCP(最初の描画)がよくなっても、TBT や INP(操作への応答)を大きく悪くしうる
JavaScript を待つあいだ残りの JavaScript を読み込んでいるあいだも、利用者はページを操作できるクリックやタップをしても何も起きない時間がある

最初に見えるまでの速さと、操作できるまでの速さは別のものです。 ハイドレーションは前者を縮め、後者のためには同じ画面をブラウザでもう一度処理する仕事を残しました。 2022年4月、Builder.io の Miško Hevery は「Hydration is Pure Overhead」で、“Hydration is overhead because it duplicates work.”(ハイドレーションは仕事を重複させるので上乗せだ)と評しています。

ページ全体をハイドレーションしない

Section titled “ページ全体をハイドレーションしない”

2020年8月11日、Jason Miller は「Islands Architecture」で、ページの HTML はサーバーで描き、動きの多い部分のまわりにだけ “placeholders or slots”(場所取りの枠)を入れる形を書きました。この考え方と Astro などの実装は、アイランドアーキテクチャの記事で扱います。 React のチームは2020年12月21日に React Server Components の研究を発表し、Angular は2026年6月3日の 22.0.0 で、incremental hydration(段階的なハイドレーション)を既定にしています。

2026年10月29日に npm で確かめた、主なフレームワークの版とハイドレーションの入口です。

パッケージ版(公開日)ハイドレーションの入口直近1か月のダウンロード
react-dom19.3.1(2026-10-14)hydrateRoot(domNode, reactNode)691,009,512
vue3.5.44(2026-10-13)createSSRApp67,881,916
svelte5.57.3(2026-10-26)hydrate(component, { target })26,070,664
@angular/platform-browser22.2.3(2026-10-14)provideClientHydration()21,719,136

ダウンロード数は2026年9月28日から10月27日までの npm の数字で、ハイドレーションを使っているサイトの割合ではありません。

React は2024年12月5日の 19.0.0 で ReactDOM.render と ReactDOM.hydrate を削除し、hydrateRoot だけが残りました。ドキュメントは、描いた中身がサーバーの中身と同じであることを hydrateRoot が期待すると書いています。Vue には、食い違いを許す場所を示す data-allow-mismatch 属性があります。 Angular では、provideClientHydration() を使うと incremental hydration が既定で有効になり、それまでの withIncrementalHydration は22.0 から非推奨です。 2026年10月時点で、主なフレームワークはどれもハイドレーションの入口を持ち、React の selective hydration や Angular の incremental hydration のように、ページ全体を一度にではなく部分ごとにハイドレーションする仕組みを備えています。

  • 2013年の Airbnb は、同じ JavaScript のコードでサーバーが HTML を描くようにしました。中身はすぐ見えても、ただの HTML は押しても動かず、捨てて描き直せば利用者の操作が乱れるおそれがありました。
  • Airbnb はサーバーの HTML を捨てずに、data-view を目印にビューを付け直しました。React は同じ年にチェックサムでサーバーの HTML を使い回しはじめます。
  • React がサーバーの HTML につなぐ API を hydrate と名付けて render から分けたのは2017年の React 16 で、「つなぐ」か「消す」かの取り違えを避けるためでした。語を最初に使った例は確かめられていません。
  • 見えるのは速くなっても、操作できるまでには同じ画面をもう一度処理する時間が残りました。selective hydration・アイランド・incremental hydration は、その範囲を減らす試みです。
  • 2026年10月時点で、React・Vue・Svelte・Angular はどれもハイドレーションの入口を持ち、React は hydrateRoot に一本化しています。

年表

  1. Airbnb の Spike Brehm が、Backbone をサーバーでも動かす Rendr でモバイルサイトを作り直し、サーバーの HTML にビューを付け直す方法を書く web.archive.org

  2. React 0.4.1 が、サーバーで描いた HTML とクライアントの食い違いを見つけるチェックサムを加える github.com

  3. Yahoo の Fluxible に公開に向けたコミットが入り、その README がサーバーの状態を dehydrate してブラウザで rehydrate する仕組みを書く github.com

  4. React 16 がチェックサムをやめ、サーバーの HTML につなぐ専用の API `ReactDOM.hydrate` を加える legacy.reactjs.org

  5. Google の「Rendering on the Web」が、rehydration が操作できるまでの時間を遅らせる問題を指摘する developers.google.com

  6. Jason Miller が、動く部分だけを島のように扱う「Islands Architecture」を書く jasonformat.com

  7. React 18 が `hydrateRoot` を加え、操作された部分から先にハイドレーションする selective hydration を入れる react.dev

  8. React 19 で `ReactDOM.render` と `ReactDOM.hydrate` が削除され、`hydrateRoot` に一本化される github.com

系譜

この技術が出てくる物語

同時代の出来事

出典

  1. 一次Our First Node.js App: Backbone on the Client and Server(Spike Brehm, Airbnb Engineering, 2013-01-30) nerds.airbnb.com 保存版
  2. 一次React CHANGELOG.md github.com
  3. 一次Settle on Heuristic/API for Choosing Hydration(Sebastian Markbåge, facebook/react issue github.com
  4. 一次Hydration of previously rendered server markup(Sebastian Markbåge, facebook/react PR github.com
  5. 一次Add ReactDOM.hydrate() as explicit SSR hydration API(Dan Abramov, facebook/react PR github.com
  6. 一次React v16.0(React Blog, 2017-09-26) legacy.reactjs.org
  7. 一次ReactDOM(React 16 のドキュメント) 16.reactjs.org
  8. 一次Partial Hydration(Sebastian Markbåge, facebook/react PR github.com
  9. 一次React v18.0(React Blog, 2022-03-29) react.dev
  10. 一次New in 18: Selective Hydration(reactwg/react-18 Discussion #130, 2022-03-28) github.com
  11. 一次Introducing Zero-Bundle-Size React Server Components(React Blog, 2020-12-21) legacy.reactjs.org
  12. 一次yahoo/fluxible の README(2014-10-28 のコミット「Ready for open source」) github.com
  13. 一次Vue SSR Guide「Client Side Hydration」(2018年7月時点) web.archive.org
  14. 一次Server-Side Rendering API(Vue.js のドキュメント) vuejs.org
  15. 一次Rendering on the Web(Google, 2019-02-06、2026-01-05 更新) developers.google.com 保存版
  16. 一次Islands Architecture(Jason Miller, 2020-08-11) jasonformat.com
  17. 一次Imperative component API(Svelte のドキュメント) svelte.dev
  18. 一次Incremental Hydration(Angular のドキュメント) angular.dev
  19. 一次withIncrementalHydration(Angular の API リファレンス) angular.dev
  20. 一次npm registry(react-dom) registry.npmjs.org
  21. 一次npm download counts(react-dom、直近1か月) api.npmjs.org
  22. 二次Hydration is Pure Overhead(Miško Hevery, Builder.io, DEV, 2022-04-20) dev.to