Reactは、モダンなWebアプリケーション開発において不可欠なライブラリとして、多くの開発者に利用されています。その中でも「ハイドレーション (hydration)」は、特にサーバーサイドレンダリング (SSR) や静的サイト生成 (SSG) の文脈で重要な役割を果たします。この記事では、Reactのハイドレーションの仕組み、使い方、そして開発者が押さえておくべきポイントについて詳しく解説します。
[目次を開く]
ハイドレーションとは?
ハイドレーションは、サーバーでレンダリングされたHTMLコンテンツにクライアントサイドでJavaScriptの機能を追加して、完全なインタラクティブなWebアプリケーションを構築するプロセスを指します。簡単に言うと、静的なHTMLにReactの「命」を吹き込む作業です。
ハイドレーションの基本的な流れ
- サーバーサイドレンダリング (SSR)
サーバー側でReactコンポーネントをレンダリングし、静的なHTMLを生成します。このHTMLには、データと状態が埋め込まれた形でクライアントに送信されます。
- クライアントサイドでの再接続
ブラウザがHTMLを受け取ると、ReactのJavaScriptコードが実行され、サーバーで生成されたDOMにReactのイベントリスナーが追加されます。
- インタラクティブ化
これにより、HTMLはインタラクティブなReactアプリケーションとして動作します。
なぜハイドレーションが必要なのか?
Reactのハイドレーションは、主に以下のようなユースケースで必要になります。
1. サーバーサイドレンダリング(SSR)の利点を活かすため
SSRは、初期ロードの速度を改善し、SEOパフォーマンスを向上させるために使用されます。しかし、SSRだけでは静的なHTMLしか提供できません。ここでハイドレーションを用いることで、クライアント側でReactの機能が有効になり、インタラクティブな動作が可能になります。
2. ユーザー体験の向上
ハイドレーションにより、初期ロード時の高速な描画と、リッチなインタラクションが両立します。特にユーザーがページを操作する直前まで、Reactがバックグラウンドで動作準備を整えるため、スムーズな体験を提供できます。
ハイドレーションの実装例
以下に、Reactでハイドレーションを実装する基本的なコード例を示します。
// サーバーサイド (例: Express.js)
import express from 'express';
import ReactDOMServer from 'react-dom/server';
import App from './App';
const app = express();
app.get('*', (req, res) => {
const html = ReactDOMServer.renderToString(<App />);
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>React Hydration Example</title>
</head>
<body>
<div id="root">${html}</div>
<script src="/bundle.js"></script>
</body>
</html>
`);
});
app.listen(3000, () => {
console.log('Server is running on http://localhost:3000');
});
// クライアントサイド
import React from 'react';
import { hydrateRoot } from 'react-dom/client';
import App from './App';
const container = document.getElementById('root');
hydrateRoot(container, <App />); 上記のコードでは、hydrateRootを使用してサーバーから送信されたHTMLにReactのインタラクティブな機能を追加しています。
ハイドレーションの課題と注意点
1. サーバーとクライアントの不一致
サーバーで生成されたHTMLとクライアントでレンダリングされたDOMが一致しない場合、Reactは警告を出します。これを避けるために、サーバーとクライアントの状態が常に一致するようにする必要があります。
2. 初期ロード時のパフォーマンス
ハイドレーションは、初期ロードのタイミングでJavaScriptを大量に読み込む必要があるため、パフォーマンスが低下する可能性があります。この場合、コード分割や遅延ロードの導入が効果的です。
3. 動的コンテンツの扱い
SSRで動的なデータを扱う場合、データが最新であることを保証する仕組みが必要です。これには、サーバーとクライアント間の同期を適切に設計することが求められます。
ハイドレーションエラーはメッセージで原因が絞り込める
| エラーメッセージ(先頭部分) | よくある原因 | 最初に見る場所 |
|---|---|---|
Text content does not match server-rendered HTML | 時刻・乱数・ロケール依存の値を描画のたびに生成している | JSX の中の new Date() Math.random() toLocaleString() |
Hydration failed because the initial UI does not match | サーバーとクライアントで描画を分岐させている | typeof window !== 'undefined' や localStorage の参照 |
Expected server HTML to contain a matching div in p | HTML として不正な入れ子をブラウザが勝手に組み替えている | 該当コンポーネントのタグの入れ子 |
There was an error while hydrating this Suspense boundary | 失敗が Suspense 境界の内側だけに閉じている | その境界の中のコンポーネントだけ |
サーバーの HTML とブラウザの DOM を突き合わせる
原因のコンポーネントが特定できないときは、JavaScript を実行していない状態の HTML を取って比べます。$PAGE_URL には対象ページのアドレスを入れてください。差分が出た行のテキストが、そのままズレている値です。
curl -s "$PAGE_URL" > server.html
# DevTools の Elements で html 要素を右クリック → Copy outer HTML し client.html として保存
diff <(sed 's/></>\n</g' server.html) <(sed 's/></>\n</g' client.html) | head -40 差分が出ないのに警告が消えない場合は、広告ブロッカーなどの拡張機能が DOM を書き換えている可能性があります。拡張機能を無効にしたウィンドウで再現するかを先に確認してください。
原因別の直し方
| 状況 | 対処 | 注意 |
|---|---|---|
| 時刻・乱数などサーバーと必ずズレる値 | マウント後に useEffect で入れる | 初回描画はプレースホルダになる |
localStorage や window に依存する表示 | クライアント専用にして SSR から外す | 初期表示が遅れるので画面全体には使わない |
| ズレてよい要素が1つだけ | suppressHydrationWarning を付ける | その要素の直下のテキストにしか効かず、子孫には及ばない |
// サーバーと必ずズレる値は初回描画では出さず、マウント後に入れる
import { useEffect, useState } from 'react'
export function Clock() {
const [now, setNow] = useState<string | null>(null)
useEffect(() => setNow(new Date().toLocaleTimeString()), [])
return <time>{now ?? '--:--:--'}</time>
}
// Next.js: クライアントでしか動かない部品は SSR から外す
import dynamic from 'next/dynamic'
const Chart = dynamic(() => import('./Chart'), { ssr: false }) Reactハイドレーションの進化:SuspenseやStreaming
React 18では、ハイドレーションのパフォーマンスをさらに向上させるために「部分的なハイドレーション」や「ストリーミングサーバーレンダリング」が導入されました。これにより、大規模なアプリケーションでも効率的なハイドレーションが可能になっています。
- 部分的ハイドレーション:必要な部分だけを優先的にハイドレーションすることで、初期ロードの速度を向上。
- ストリーミング:サーバーが生成したHTMLを順次クライアントに送信し、即座にハイドレーションを開始。
まとめ
Reactのハイドレーションは、SSRやSSGでのパフォーマンス向上やインタラクティブな体験を実現するための重要な技術です。サーバーとクライアント間での適切な同期や、パフォーマンスの最適化を意識することで、ユーザーにとって快適なWebアプリケーションを提供できます。
Reactの最新機能を活用し、より効率的で魅力的なアプリケーションを構築していきましょう。
