React Router v7 の内部構造を探る:リクエストからレンダリングまでの道のり
はじめに
React Router は、React アプリケーションにおけるルーティングライブラリのデファクトスタンダードとして長年利用されてきました。v6 で Data API が導入され、フルスタックフレームワークとしての側面が強化されましたが、v7 ではさらに進化し、Vite との統合、Single Fetch、Lazy Loading といったモダンな機能がデフォルトで組み込まれ、より洗練された開発体験とパフォーマンスを提供します。
しかし、これらの機能がどのように連携し、ブラウザのリクエストがどのように処理され、最終的にページが表示されるのか、その内部構造は少し複雑に見えるかもしれません。
この記事では、React Router v7 で構築されたアプリケーションの動作フローを、主要なパッケージやコンポーネントの役割、データ取得の仕組み、レンダリングプロセスなどに焦点を当てて、内部構造の観点から解説します。具体的なコード例よりも、全体の流れと重要な概念の理解を目的とし、適宜 GitHub のソースコードへのリンクを付記します。
対象読者:
- React Router v6/v7 をある程度利用したことがある開発者
- React Router の内部的な仕組みやデータフローに興味がある方
- SSR/CSR、Single Fetch、Lazy Loading といった概念がどのように実装されているか知りたい方
パッケージ構成の概観
React Router v7 はモノレポ構成を採用しており、機能ごとに複数のパッケージに分割されています。主要なパッケージとその役割を理解することが、全体の流れを把握する第一歩となります。

-
react-router: (packages/react-router)- React Router のコアロジックを提供します。
-
<Routes>,<Route>,<Outlet>,<Navigate>といった基本的なコンポーネント。 -
useLocation,useParams,useNavigate,useRoutes,useLoaderData,useActionDataなどのコアフック。 - プラットフォーム非依存のルーターカーネル (
@remix-run/routerから内部化)。 - SSRやSingle Fetchに関連するコンポーネント (
<Meta>,<Links>,<Scripts>など) もv7からこのパッケージに含まれます。
-
react-router-dom: (packages/react-router-dom)- DOM 環境(Web ブラウザ)向けの API を提供します。
-
<BrowserRouter>,<HashRouter>,<Link>,<NavLink>など。 - v7 では、その多くの機能が
react-router本体に統合され、主に後方互換性やDOM特化のAPI(<RouterProvider>のReactDOM.flushSync利用など)を提供します。実質的にはreact-routerを再エクスポートする部分が大きいです。
-
@react-router/dev: (packages/react-router-dev)- 開発体験を向上させるためのツールと CLI を提供します。
- Vite プラグイン (
reactRouter()): HMR、コード分割、SSR設定などを Vite と統合します。 (関連コード: packages/react-router-dev/vite/plugin.ts) - CLI コマンド (
react-router dev,react-router build): 開発サーバーの起動や本番ビルドを実行します。 (関連コード: packages/react-router-dev/cli/run.ts) - 型生成 (
typegen): ルート定義に基づいて型定義を自動生成します。 (関連コード: packages/react-router-dev/typegen/index.ts)
-
@react-router/create-react-router: (packages/create-react-router)-
npm create react-routerコマンドの実体で、プロジェクトの雛形を生成します。
-
-
@react-router/node,@react-router/express,@react-router/cloudflareetc.:- 各サーバープラットフォーム用の アダプター を提供します。
- プラットフォーム固有の Request/Response オブジェクトを標準の Web API に変換したり、プラットフォーム固有の機能(セッションストレージなど)を提供したりします。
-
createRequestHandlerをエクスポートし、サーバーフレームワークとの連携を担います。 (例: packages/react-router-express/server.ts#createRequestHandler)
-
@react-router/serve: (packages/react-router-serve)- 本番環境用のシンプルな Node.js アプリケーションサーバー。
@react-router/expressを内部で使用しています。
- 本番環境用のシンプルな Node.js アプリケーションサーバー。
-
@react-router/fs-routes: (packages/react-router-fs-routes)- ファイルシステムベースのルーティング規約(Remix v2 形式)を提供します。
routes.ts内で使用できます。
- ファイルシステムベースのルーティング規約(Remix v2 形式)を提供します。
-
@react-router/remix-routes-option-adapter: (packages/react-router-remix-routes-option-adapter)- 従来の Remix の
remix.config.js内のroutesオプション形式をroutes.tsで使用するためのアダプター。
- 従来の Remix の
起動から初回表示までの道のり (SSRフロー)
ユーザーが最初にページにアクセスした際の、サーバーサイドでの処理の流れを見ていきましょう。

-
[サーバー] リクエスト受信とアダプター
- ブラウザからのリクエストが Express などのサーバーに到着します。
-
@react-router/expressのcreateRequestHandlerで生成されたハンドラーが呼び出されます。 - アダプターは Express の
reqオブジェクトなどから標準のRequestオブジェクトを作成します。
-
[サーバー] ルートマッチングとデータ取得 (Core Handler)
- アダプターから
react-routerのコアハンドラー (createRequestHandlerで作成されたもの) が呼び出されます。 - 内部で
createStaticHandlerが利用され、URL パス名に基づいてルート定義 (build.routes) と照合し、マッチするルート (matches) を特定します。 -
staticHandler.query()が呼び出され、マッチした各ルートのloader関数が 並列 で実行されます。 - すべての
loaderが完了すると、結果 (データ、リダイレクト、エラー) を含むStaticHandlerContextが生成されます。
- アダプターから
-
[サーバー] サーバーサイドレンダリング (SSR)
- サーバーエントリーポイント (
entry.server.tsxのdefaultexport) が呼び出されます。 - React の
renderToPipeableStream(または環境に応じたAPI) が<ServerRouter>コンポーネントをレンダリングします。 -
<ServerRouter>は内部でcreateStaticRouterを使い、StaticHandlerContextから受け取ったデータで初期化されたルーターインスタンスを作成します。 - React コンポーネントツリー (
root.tsxから<Outlet>を通じて) がレンダリングされます。 -
<Meta />,<Links />,<Scripts />が適切なタグをHTMLに挿入します。-
<Scripts />は、ハイドレーションに必要なデータ (loaderData,actionData,errors) をwindow.__reactRouterContextとしてインライン<script>にシリアライズし、クライアントJSのエントリーポイントを読み込む<script type="module">も生成します。 - (関連コード: packages/react-router/lib/dom/ssr/components.tsx#Scripts)
-
- サーバーエントリーポイント (
-
[サーバー] レスポンス送信
- レンダリングされたHTMLストリームと、収集されたヘッダー(
Set-Cookieなど)を含むResponseオブジェクトが構築されます。 - プラットフォームアダプターがこの
Responseをサーバーフレームワークのレスポンスに変換してクライアントに送信します。
- レンダリングされたHTMLストリームと、収集されたヘッダー(
クライアントサイドの魔法: ハイドレーション
サーバーから送られてきたHTMLを、ブラウザ上でインタラクティブなReactアプリケーションとして復元するプロセスです。

-
[ブラウザ] アセット受信と解析:
- ブラウザはHTMLを受信し、DOMツリーを構築します。
<link>タグによりCSSやJSモジュールのダウンロードが始まります。
- ブラウザはHTMLを受信し、DOMツリーを構築します。
-
[ブラウザ] クライアントエントリー実行:
-
<Scripts />が出力した<script type="module">によりentry.client.tsxが実行されます。
-
-
[ブラウザ/React] ハイドレーション:
-
hydrateRootが呼び出されます。 -
<HydratedRouter>(または<RouterProvider>) がレンダリングされます。 - 内部で
createHydratedRouterが呼び出され、window.__reactRouterContext等からサーバーの状態 (loaderData,errorsなど) を読み取ります。Single Fetch のストリームデータもこのタイミングでデコードされ始めます。 -
createBrowserRouter(またはcreateHashRouter) を使ってクライアントルーターが初期化されます。 - React はサーバー生成のHTMLとクライアント生成のコンポーネントツリーを比較し、イベントリスナーをアタッチしてDOMをインタラクティブにします。
-
-
[ブラウザ] 完了:
- ハイドレーションが完了し、アプリケーションが操作可能になります。
useEffectなどが実行され、必要に応じて<ScrollRestoration>がスクロール位置を調整します。
- ハイドレーションが完了し、アプリケーションが操作可能になります。
ページ遷移の裏側 (クライアントサイドナビゲーション)
ハイドレーション後、ユーザーが <Link> をクリックした際の動作です。

-
<Link>クリックとイベント抑制:- クリックイベントが発生しますが、
useLinkClickHandlerがevent.preventDefault()を呼び、ブラウザのデフォルト動作をキャンセルします。
- クリックイベントが発生しますが、
-
router.navigate():-
<Link>はrouter.navigate()を呼び出し、React Routerにナビゲーションの開始を伝えます。
-
-
状態更新とHistory API:
- ルーターは
state.navigationを'loading'に更新します。 - History API (
pushStateまたはreplaceState) を操作してブラウザのURLを更新します。
- ルーターは
-
ルートマッチングとLazy Loading:
- 新しいURLに対して
matchRoutesが実行されます。 - マッチしたルートに
lazyがあれば、モジュール読み込みが開始されます。- (関連コード: packages/react-router/lib/router/router.ts#matchLoaderMatches 付近)
- (関連コード: packages/react-router/lib/dom/ssr/routes.tsx#createClientRoutes の
lazyプロパティ)
- 新しいURLに対して
-
Single Fetch (Loader):
- 再検証が必要な
loader(またはclientLoader) を特定します (shouldRevalidate考慮)。 - 必要なルートIDを含む
.dataエンドポイント (例:/page-b.data?_routes=id1,id2) に対して 単一の GET fetchリクエスト を送信します。 - サーバー側 (
singleFetchLoaders) は対応するloaderを実行し、結果をエンコードして返します。 - クライアントはレスポンスをデコード (
decodeViaTurboStream) します。
- 再検証が必要な
-
状態更新とUIレンダリング:
- Lazy Loadingとデータ取得が完了したら、
state.loaderData等が更新されます。 -
<RouterProvider>がトリガーされ、useRoutesImplが新しいコンポーネントツリーを計算し、<Outlet>などが更新されます。
- Lazy Loadingとデータ取得が完了したら、
-
スクロール復元:
-
<ScrollRestoration>が遷移前ページのスクロール位置を保存し、遷移先ページのスクロール位置を復元(またはリセット)します。
-
-
ナビゲーション完了:
-
state.navigationが'idle'に戻ります。
-
データの変更 (Form Submission / Action)
<Form> 送信時の流れはナビゲーションと似ていますが、Actionの実行とそれに続くRevalidationが含まれます。

-
<Form>サブミットとイベント抑制:- サブミットイベントが発生し、
event.preventDefault()でデフォルト動作をキャンセルします。
- サブミットイベントが発生し、
-
router.navigate():- フォームの情報(action, method, formDataなど)を使って
router.navigate()が呼び出されます。
- フォームの情報(action, method, formDataなど)を使って
-
状態更新 (Submitting):
-
state.navigationが'submitting'になり、フォームデータなどが格納されます。
-
-
ルートマッチングとLazy Loading (Action):
- Actionに対応するルートを特定し、必要なら
lazy()を実行します。
- Actionに対応するルートを特定し、必要なら
-
Single Fetch (Action):
-
.dataエンドポイントに 単一の POST (等) fetchリクエスト を送信します。 - サーバー側 (
singleFetchAction) は対応するaction関数を実行します。 - クライアントはレスポンス (Action Data または Redirect) を受け取ります。
-
-
状態更新とリダイレクト/Revalidation:
-
state.actionDataが設定されます。 - Actionがリダイレクトを返した場合、新しいナビゲーションが
replace: trueで開始されます。 - リダイレクトがない場合、Revalidation がトリガーされます。関連する
loaderが再度 Single Fetch (GET) で呼び出されます。
-
-
UIレンダリング、スクロール処理、完了:
- ナビゲーションと同様に、UIが更新され、スクロールが処理され、
state.navigationが'idle'に戻ります。
- ナビゲーションと同様に、UIが更新され、スクロールが処理され、
キーとなる概念の深掘り
-
Vite統合 (
@react-router/dev): ビルド時の最適化(コード分割)、開発時の高速な HMR、サーバーリクエストハンドリングの仲介など、React Router をフレームワークとして機能させるための重要な役割を担います。 - Single Fetch: ナビゲーションやサブミッション時に発生する複数のデータ取得/更新リクエストを1つにまとめる仕組みです。これにより、ネットワークのオーバーヘッドを削減し、ウォーターフォール問題を緩和します。サーバー側とクライアント側で協調して動作します。
-
Lazy Loading: ルート定義時に
lazy()関数を指定することで、そのルートが実際に必要になるまで関連モジュールの読み込みを遅延させます。初期バンドルサイズを削減し、アプリケーションの起動時間を短縮します。 -
<RouterProvider>と状態管理: ルーターの現在の状態(location, loaderData, navigation stateなど)を一元管理し、コンテキストを通じて配下のコンポーネントに提供します。これにより、フック (useLocation,useLoaderData,useNavigationなど) を使って状態にアクセスできます。 - アダプター: Express, Cloudflare Workers, Node.js httpサーバーなど、様々な実行環境の差異を吸収し、共通のインターフェースでReact Routerを利用可能にします。
まとめ
React Router v7 は、単なるルーティングライブラリを超え、データ取得、ミューテーション、レンダリング、開発ツールを統合したフルスタックに近いフレームワークへと進化しました。Vite との緊密な連携、Single Fetch による効率的なデータ通信、Lazy Loading によるパフォーマンス最適化などがその核となる特徴です。
この記事で解説した内部構造の流れを理解することで、アプリケーションの動作をより深く把握し、デバッグやパフォーマンスチューニングに役立てることができるでしょう。
参考資料:
- React Router 公式ドキュメント: https://reactrouter.com/
- React Router GitHub リポジトリ: https://github.com/remix-run/react-router
免責事項: この記事は React Router v7.4.1 時点の情報に基づいており、内部実装は将来のバージョンで変更される可能性があります。GitHub のリンクは特定のコミットハッシュ (252d928...) を指していますが、最新の情報はリポジトリの最新状態を確認してください。
Discussion