状況
/dashboard に移動する — またはそこへの <Link> をクリックする — と画面が真っ白になります。コンポーネントも、エラー境界も、何も表示されません。コンソールにはこう表示されます:
No routes matched location "/dashboard"
React Router v6 は <Routes> ブロック内のすべての <Route> をスキャンしましたが、一致するものが見つかりませんでした。よくある原因は5つあります。一つずつ確認していきましょう。
デバッグ手順
1. ルートツリーの実際の構成を確認する
ルートを定義しているコンポーネントを開き、パスを一文字ずつ読み確認してください。大文字が一つあるだけですべてが壊れます:
// 間違い — path が "Dashboard"(大文字のD)
<Route path="/Dashboard" element={<Dashboard />} />
// Link は小文字で指定している
<Link to="/dashboard">Go</Link>
React Router のパスは大文字・小文字を区別します。/Dashboard と /dashboard は完全に別のルートです — 自動的に合わせてくれるようなことはありません。
2. の内側にルートがあるか確認する
<Routes> コンポーネントの外側に置かれた <Route> は、エラーも出さずに無視されます。何も起こらないだけです:
// 間違い — Route が Routes の外にある
<BrowserRouter>
<Route path="/dashboard" element={<Dashboard />} />
</BrowserRouter>
// 正しい
<BrowserRouter>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</BrowserRouter>
3. ネストされたルートのパスを確認する
React Router v6 では、子ルートのパスは相対パスです。/ を先頭に付けたり、親のセグメントを繰り返したりしてはいけません:
// 間違い — 子パスが / で始まっている
<Route path="/app" element={<AppLayout />}>
<Route path="/app/dashboard" element={<Dashboard />} />
</Route>
// 正しい — 子パスは相対パス
<Route path="/app" element={<AppLayout />}>
<Route path="dashboard" element={<Dashboard />} />
</Route>
子ルートに path="/app/dashboard" と書くと、React Router はそれをルートからの絶対パスとして扱います — 親からの相対パスではありません。dashboard とだけ書けば、完全な URL は自動的に /app/dashboard に解決されます。
4. (または相当品)がアプリ全体をラップしているか確認する
<Routes> の上位にルーターコンテキストがなければ、何も機能しません。エントリーポイントをよく確認してください:
// main.tsx または index.tsx
import { BrowserRouter } from 'react-router-dom';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<BrowserRouter>
<App />
</BrowserRouter>
);
よくある落とし穴:Remix や TanStack Router を使っている場合、その上に <BrowserRouter> を追加してはいけません。これらのフレームワークは独自のルーターコンテキストを提供しています。二つのルーターコンテキストが重なると互いに干渉し、まさにこのエラーを引き起こします。
5. ルートを隠す条件付きレンダリングに注意する
これは見落としやすいパターンです。ルートの定義が、最初のレンダリング時に false になる条件の中に入っています:
// バグ: ルートがユーザー読み込み後にしかレンダリングされない
function App() {
const { user } = useAuth();
return (
<Routes>
{user && <Route path="/dashboard" element={<Dashboard />} />}
</Routes>
);
}
ページ読み込み時、user はまだ null です。ルートはまだ存在しません。そのため /dashboard に直接アクセスすると、user が 50ms 後に truthy になるとしても、即座に No routes matched が発生します。ルートは常に無条件で定義してください。アクセス制御はコンポーネント内またはラッパーで行いましょう:
function RequireAuth({ children }: { children: JSX.Element }) {
const { user } = useAuth();
if (!user) return <Navigate to="/login" replace />;
return children;
}
function App() {
return (
<Routes>
<Route
path="/dashboard"
element={
<RequireAuth>
<Dashboard />
</RequireAuth>
}
/>
<Route path="/login" element={<Login />} />
</Routes>
);
}
完全な動作例
以下は最小構成の正しい React Router v6 アプリです — 何かおかしいときの参考としてコピーしておいてください:
// main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
// App.tsx
import { Routes, Route, Navigate } from 'react-router-dom';
import Dashboard from './pages/Dashboard';
import Login from './pages/Login';
import Home from './pages/Home';
export default function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/login" element={<Login />} />
{/* キャッチオール — 真っ白な画面の代わりに404を表示 */}
<Route path="*" element={<div>404 Not Found</div>} />
</Routes>
);
}
末尾の path="*" キャッチオールはすべてのプロジェクトに追加する価値があります。真っ白な画面はデバッグの手がかりを何も与えてくれません。404 ページがあれば、少なくともルーターが動いていてパスが単に一致しないということがわかります。
修正の確認
- ブラウザで URL に直接移動する(ハードリフレッシュ)。正しいコンポーネントがレンダリングされるはずです。
<Link to="/dashboard">をクリックする — ページ全体のリロードなしに画面遷移が行われるはずです。- React DevTools を開き、ルーターコンテキストを見つけて、マッチしたルートが
/dashboardになっているか確認する。 - ブラウザのコンソールを確認する — No routes matched の警告が消えているはずです。
補足:ハッシュルーターとブラウザルーター
静的デプロイ(サーバーサイドルーティングなし)には別の落とし穴があります。アドレスバーに /dashboard と直接入力すると、ブラウザはそのリクエストをサーバーに送信します。サーバーは index.html しか知らないため、React が読み込まれる前に 404 を返してしまいます。
対処方法は2つあります:
- すべてのパスを
index.htmlにリダイレクトするようサーバーを設定する — Nginx、Apache、または Cloudflare Pages での正しい修正方法です。 <HashRouter>に切り替える — URL が/#/dashboardの形式になり、クライアントから外に出ることがなくなります。
// SPA 向け Nginx 設定
location / {
try_files $uri $uri/ /index.html;
}
症状は同じように見えますが、原因は異なります。ネットワークタブを開いてください。HTML ファイルへの 200 レスポンスがあり、かつコンソールに No routes matched が表示されている場合はルーティング設定の問題です。サーバーから直接 404 が返ってくる場合はデプロイ設定の問題です。この一つの確認で、二つのケースをすぐに区別できます。

