はじめに
Reactでクライアントサイドルーティングを実装するとき、最初に候補に挙がるのがReact Routerです。
12年以上の歴史を持ち、GitHubで56,000スター以上、月間ダウンロード数は約8,000万件。Reactのエコシステムの中でも、圧倒的な存在感を持つライブラリです。1,100万以上のプロジェクトで使われているという数字が、その信頼性を物語っています。
正直なところ、最近はTanStack RouterやNext.jsのApp Routerなど、ルーティングの選択肢が増えてきて「React Routerはもう古いのでは」という声も聞こえてきます。でも、v7のリリースで状況は変わりました。Remixチームとの統合により、フレームワークとしての機能も備えるようになったんですよ。
React Routerとは
React Routerは、Reactアプリケーション向けの宣言的ルーティングライブラリです。URLとUIコンポーネントを対応づけて、ブラウザの履歴管理やナビゲーションを簡単に実装できます。
現在の最新バージョンはv7.11.0(2025年12月リリース)。Remix Runチームが開発を主導しており、MITライセンスで公開されています。
v7の大きな変更点として、「マルチストラテジールーター」というコンセプトが導入されました。React 18からReact 19への移行をサポートしつつ、フレームワークとしてもライブラリとしても使えるようになっています。
特徴・メリット
1. 非破壊的なアップグレード
v6からv7へのアップグレードは、基本的に非破壊的です。既存のコードをほぼそのままに、新機能を段階的に採用できる。これ、大規模プロジェクトだと本当にありがたい話です。
メジャーバージョンアップで動かなくなる恐怖は、エンジニアなら誰でも経験があるはず。React Routerはその点、丁寧に移行パスを用意してくれています。
2. ネストルートとレイアウトの管理
React Routerの真価は、ネストルートの扱いやすさにあります。親ルートで共通レイアウトを定義し、子ルートで個別のコンテンツを描画する。この構造が宣言的に書けるのは、大規模アプリケーションで特に効いてきます。
Outletコンポーネントを使えば、レイアウトの入れ子構造がすっきり表現できる。ヘッダーやサイドバーを共通化しつつ、メインコンテンツだけ切り替えるパターンが簡単に実装できます。
3. データローディングの統合
v6.4以降で導入されたloader/action機能により、ルートレベルでのデータ取得が可能になりました。コンポーネントがレンダリングされる前にデータを取得できるので、ローディング状態の管理がシンプルになります。
Remixの思想を取り入れた設計で、サーバーサイドとの連携も視野に入っている。SPAからフルスタックへの移行も段階的に進められます。
4. 型安全性の向上
v7では型ジェネレーション機能が強化されました。ルートパラメータやローダーデータの型が自動推論されるようになり、TypeScriptとの相性が良くなっています。
個人的には、この改善はかなり嬉しい。パラメータの型ミスによるランタイムエラーが減るのは、QOL的にも大きいです。
5. 豊富なエコシステムと情報量
12年の歴史があるので、Stack Overflowの回答やブログ記事、チュートリアルの数が圧倒的に多い。困ったときに解決策が見つかりやすいのは、実務では重要なポイントです。
1,120人以上のコントリビューター、10,600以上のコミット。コミュニティの活発さは、ライブラリの信頼性を測る指標の一つですね。
インストール方法
新規プロジェクトの場合
フレームワークとして使う場合は、CLIツールでプロジェクトを作成できます。
npx create-react-router@latest my-app
cd my-app
npm install
npm run dev
既存プロジェクトへの導入
ライブラリとして使う場合は、パッケージをインストールするだけです。
# npm
npm install react-router
# yarn
yarn add react-router
# pnpm
pnpm add react-router
React 18以上が必要です。React 19にも対応しているので、最新環境でも問題なく動作します。
基本的な使い方
ルーターの設定
まず、ルーター設定を作成します。
// src/main.tsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { createBrowserRouter, RouterProvider } from 'react-router'
import { Root } from './routes/root'
import { Home } from './routes/home'
import { About } from './routes/about'
const router = createBrowserRouter([
{
path: '/',
element: <Root />,
children: [
{ index: true, element: <Home /> },
{ path: 'about', element: <About /> },
],
},
])
createRoot(document.getElementById('root')!).render(
<StrictMode>
<RouterProvider router={router} />
</StrictMode>
)
ルートコンポーネントの実装
// src/routes/root.tsx
import { Outlet, Link } from 'react-router'
export function Root() {
return (
<div>
<header>
<nav>
<Link to="/">ホーム</Link>
<Link to="/about">About</Link>
</nav>
</header>
<main>
<Outlet />
</main>
</div>
)
}
Outletコンポーネントが子ルートのコンテンツを表示する場所になります。この構造により、ヘッダーやフッターを共通化できます。
動的ルートパラメータ
// ルート設定
{
path: 'users/:userId',
element: <UserDetail />,
}
// コンポーネント
import { useParams } from 'react-router'
function UserDetail() {
const { userId } = useParams()
return <div>ユーザーID: {userId}</div>
}
データローダーの活用
// ルート設定
{
path: 'users/:userId',
element: <UserDetail />,
loader: async ({ params }) => {
const response = await fetch(`/api/users/${params.userId}`)
if (!response.ok) {
throw new Response('Not Found', { status: 404 })
}
return response.json()
},
}
// コンポーネント
import { useLoaderData } from 'react-router'
function UserDetail() {
const user = useLoaderData()
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
)
}
loaderでデータを取得し、useLoaderDataで受け取る。このパターンを覚えておくと、データフェッチの実装がかなり楽になります。
実践的なユースケース
認証によるルートガード
import { redirect } from 'react-router'
// 認証が必要なルート
{
path: 'dashboard',
element: <Dashboard />,
loader: async () => {
const user = await getAuthenticatedUser()
if (!user) {
return redirect('/login')
}
return { user }
},
}
loaderで認証状態をチェックし、未認証ならリダイレクト。シンプルで分かりやすいパターンです。
フォーム送信とアクション
import { Form, useActionData, redirect } from 'react-router'
// ルート設定
{
path: 'contact',
element: <Contact />,
action: async ({ request }) => {
const formData = await request.formData()
const email = formData.get('email')
const message = formData.get('message')
// バリデーション
if (!email || !message) {
return { error: '入力内容を確認してください' }
}
// 送信処理
await sendContactForm({ email, message })
return redirect('/contact/complete')
},
}
// コンポーネント
function Contact() {
const actionData = useActionData()
return (
<Form method="post">
{actionData?.error && <p>{actionData.error}</p>}
<input type="email" name="email" required />
<textarea name="message" required />
<button type="submit">送信</button>
</Form>
)
}
Formコンポーネントとactionを組み合わせると、フォーム送信がページ遷移なしで処理できます。バリデーションエラーの表示もスムーズです。
遅延ローディング
import { lazy, Suspense } from 'react'
const Dashboard = lazy(() => import('./routes/dashboard'))
// ルート設定
{
path: 'dashboard',
element: (
<Suspense fallback={<div>Loading...</div>}>
<Dashboard />
</Suspense>
),
}
大規模アプリケーションでは、ルート単位でのコード分割が効果的。初期バンドルサイズを抑えられます。
まとめ
React Routerを使うと、以下のようなメリットが得られます。
- 宣言的なルーティング定義ができる
- ネストルートでレイアウト管理が楽になる
- loaderとactionでデータ操作が統合的に書ける
- 豊富な情報とコミュニティのサポートがある
30代になって思うのは、「枯れた技術」の価値は年々上がるということ。React Routerは12年の歴史がありながら、v7で現代的な機能を取り込み続けています。新しいルーターに飛びつくのも良いですが、確実に動くことが分かっている選択肢を持っておくのも、エンジニアとして大事なことなんですよね。
Next.jsを使わないReactプロジェクトでルーティングが必要なら、React Routerは今でも有力な選択肢です。特にv7以降は、フレームワーク的な使い方もできるようになったので、要件に応じて柔軟に導入できますよ。