react-router について
React 開発者向けの React Router 構築リファレンススキルです。ナビゲーション、ルートパラメーター、ネストしたルート、データ読み込み、よくある問題を扱います。
主な機能
- 01リンクとプログラムによるナビゲーション
- 02ルートパラメーターとクエリパラメーター
- 03ネストしたルートと保護ルート
- 04ローダーとエラーバウンダリー
- 05ルーティングの実践とトラブルシューティング
活用シーン
- 01React アプリのナビゲーション設定
- 02共有レイアウトを使ったネストページの構築
- 03ルートローダーによるデータ読み込み
- 04保護ルートやルーティングエラーへの対応
MCP 対応
React Router のドキュメント取得手段として `mcp__documentation__fetch_docs` に言及しています。
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ claude-dev-suite/ claude-dev-suite --skill react-routerインストール後、Claude Code で/react-routerを入力して読み込まれたか確認します。
同梱ファイルadvanced.md
詳細
React Router
詳細な知識:React Router v6+ のパターンとデータ読み込みに関する包括的なドキュメントは、technology:
react、topic:routerを指定してmcp__documentation__fetch_docsを使用してください。
完全なリファレンス:データ読み込み(v6.4+)、アクション、コード分割、ルート設定、エラー処理、スクロール復元、モーダルルート、TypeScript との統合については、advanced.md を参照してください。
基本設定
import { BrowserRouter, Routes, Route } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/users" element={<Users />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}
ナビゲーション
Link コンポーネント
import { Link, NavLink } from 'react-router-dom';
function Navigation() {
return (
<nav>
{/* Basic link */}
<Link to="/">Home</Link>
{/* NavLink with active styling */}
<NavLink
to="/about"
className={({ isActive, isPending }) =>
isActive ? 'active' : isPending ? 'pending' : ''
}
>
About
</NavLink>
{/* With state */}
<Link to="/dashboard" state={{ from: 'home' }}>
Dashboard
</Link>
{/* Replace instead of push */}
<Link to="/login" replace>
Login
</Link>
</nav>
);
}
プログラムによるナビゲーション
import { useNavigate, useLocation } from 'react-router-dom';
function LoginButton() {
const navigate = useNavigate();
const location = useLocation();
const handleLogin = async () => {
await login();
// Navigate with state
navigate('/dashboard', {
state: { from: location },
replace: true,
});
};
const handleBack = () => {
navigate(-1); // Go back
};
return (
<div>
<button onClick={handleLogin}>Login</button>
<button onClick={handleBack}>Back</button>
</div>
);
}
ルートパラメーター
動的セグメント
<Routes>
<Route path="/users/:userId" element={<UserProfile />} />
<Route path="/posts/:postId/comments/:commentId" element={<Comment />} />
</Routes>
// Component
import { useParams } from 'react-router-dom';
function UserProfile() {
const { userId } = useParams<{ userId: string }>();
return <div>User ID: {userId}</div>;
}
function Comment() {
const { postId, commentId } = useParams();
return (
<div>
Post: {postId}, Comment: {commentId}
</div>
);
}
オプションのセグメント
<Routes>
{/* Optional lang parameter */}
<Route path="/:lang?/products" element={<Products />} />
{/* Catch-all (splat) */}
<Route path="/files/*" element={<FileExplorer />} />
</Routes>
function FileExplorer() {
const { '*': filePath } = useParams();
// /files/documents/report.pdf → filePath = "documents/report.pdf"
return <div>Path: {filePath}</div>;
}
クエリパラメーター
import { useSearchParams } from 'react-router-dom';
function ProductList() {
const [searchParams, setSearchParams] = useSearchParams();
const category = searchParams.get('category') || 'all';
const page = parseInt(searchParams.get('page') || '1');
const sort = searchParams.get('sort') || 'name';
const updateFilters = (newCategory: string) => {
setSearchParams({
category: newCategory,
page: '1', // Reset page
sort,
});
};
const nextPage = () => {
setSearchParams(prev => {
prev.set('page', String(page + 1));
return prev;
});
};
return (
<div>
<select
value={category}
onChange={(e) => updateFilters(e.target.value)}
>
<option value="all">All</option>
<option value="electronics">Electronics</option>
</select>
<ProductGrid category={category} page={page} sort={sort} />
<button onClick={nextPage}>Next Page</button>
</div>
);
}
ネストされたルート
// Route configuration
<Routes>
<Route path="/dashboard" element={<DashboardLayout />}>
<Route index element={<DashboardHome />} />
<Route path="analytics" element={<Analytics />} />
<Route path="settings" element={<Settings />} />
<Route path="users">
<Route index element={<UserList />} />
<Route path=":userId" element={<UserDetail />} />
<Route path="new" element={<NewUser />} />
</Route>
</Route>
</Routes>
// Parent layout with Outlet
import { Outlet, Link } from 'react-router-dom';
function DashboardLayout() {
return (
<div className="dashboard">
<nav className="sidebar">
<Link to="/dashboard">Home</Link>
<Link to="/dashboard/analytics">Analytics</Link>
<Link to="/dashboard/settings">Settings</Link>
<Link to="/dashboard/users">Users</Link>
</nav>
<main className="content">
{/* Child routes render here */}
<Outlet />
</main>
</div>
);
}
相対リンク
function UserDetail() {
const { userId } = useParams();
return (
<div>
<h1>User {userId}</h1>
{/* Relative to current route */}
<Link to="edit">Edit User</Link> {/* → /dashboard/users/:userId/edit */}
<Link to="../">Back to List</Link> {/* → /dashboard/users */}
<Link to="../../">Dashboard</Link> {/* → /dashboard */}
</div>
);
}
保護されたルート
import { Navigate, Outlet, useLocation } from 'react-router-dom';
function ProtectedRoute({ children }: { children?: ReactNode }) {
const { user, isLoading } = useAuth();
const location = useLocation();
if (isLoading) {
return <LoadingSpinner />;
}
if (!user) {
// Redirect to login, preserving intended destination
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children ?? <Outlet />;
}
// Usage
<Routes>
<Route path="/login" element={<Login />} />
{/* Protected routes */}
<Route element={<ProtectedRoute />}>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
<Route path="/profile" element={<Profile />} />
</Route>
</Routes>
// Role-based protection
function AdminRoute({ children }: { children?: ReactNode }) {
const { user } = useAuth();
if (user?.role !== 'admin') {
return <Navigate to="/unauthorized" replace />;
}
return children ?? <Outlet />;
}
<Routes>
<Route element={<ProtectedRoute />}>
<Route path="/dashboard" element={<Dashboard />} />
{/* Admin only */}
<Route element={<AdminRoute />}>
<Route path="/admin" element={<AdminPanel />} />
<Route path="/users/manage" element={<UserManagement />} />
</Route>
</Route>
</Routes>
ベストプラクティス
- 共通レイアウトにはネストされたルートを使用します
- データの取得にはローダーを使用します(v6.4+)
- 適切なエラーバウンダリーを実装します
- ネストされたルートでは相対リンクを使用します
- ルートコンポーネントを遅延読み込みします
- ユーザーの意図(ホバー)に応じて事前読み込みします
- ローダーが利用できる場合は、useEffect 内でデータを取得しないでください
- パスをハードコードせず、相対ナビゲーションを使用します
- 読み込み状態の処理を忘れないでください
このスキルを使用しない場合
- Next.js アプリケーション - 代わりに Next.js App Router または Pages Router を使用します
- サーバーサイドルーティング - Express など、フレームワーク固有のルーティングを使用します
- 単純な条件付きレンダリング - 基本的な表示/非表示のロジックには
reactスキルを使用します - 静的サイト - ルーティングライブラリが必要かどうかを検討してください
アンチパターン
| アンチパターン | 問題 | 解決策 |
|---|---|---|
| ローダーを利用できるのに useEffect 内で取得する | 読み込みが連鎖し、UX が低下します | ローダー関数を使用します |
| 絶対パスをハードコードする | リファクタリングが難しく、ネストされたルートが動作しなくなります | 相対パスを使用します |
| 読み込み状態を処理しない | UX が低下します | useNavigation または Suspense を使用します |
| エラーバウンダリーがない | ルートのエラーでアプリがクラッシュします | ルートに errorElement を追加します |
| ルートを遅延読み込みしない | 初期バンドルが大きくなります | ルートに React.lazy() を使用します |
| インデックスをルートのキーとして使う | 動作が不正になります | ルートにキーを使用しないでください |
| 404 の処理を忘れる | 空白ページになるかクラッシュします | path="*" のキャッチオールルートを追加します |
トラブルシューティング(クイックガイド)
| 問題 | 考えられる原因 | 修正方法 |
|---|---|---|
| ルートが一致しない | パス構文が正しくない | パスの定義を確認し、正確なパスを使用します |
| リンクが動作しない | to プロパティが正しくない | パスが / で始まっているか、相対パスであることを確認します |
| パラメーターが undefined になる | 間違ったフックを使用している | ルートパラメーターには useParams() を使用します |
| ナビゲーションが動作しない | 間違ったフックを使用している | プログラムによるナビゲーションには useNavigate() を使用します |
| ネストされたルートが表示されない | Outlet がない | 親コンポーネントに を追加します |
| ローダーが呼び出されない | RouterProvider ではなく Routes を使用している | ローダーとともに createBrowserRouter を使用します |
| 保護されたルートが動作しない | リダイレクトロジックが正しくない | 認証状態と Navigate コンポーネントを確認します |
リファレンスドキュメント
- React Router
- v6 へのアップグレード
- 高度なパターン
- MCP:
mcp__documentation__fetch_docs→ technology:react、topic:router
コメント
Markdown 対応