React Router v6 での PrivateRoute 実装
React Router v6 では、認証が必要なプライベートルートの実装方法が v5 から大きく変更されました。この記事では、よくあるエラー「Error: [PrivateRoute] is not a <Route> component」の解決策と、正しい実装方法を解説します。
問題の原因
React Router v6 では、<Routes>
の直接の子要素は <Route>
コンポーネントまたは <React.Fragment>
のみ許可されます。v5 のようにカスタムの <PrivateRoute>
コンポーネントを直接使用することはできません。
以下のような実装はエラーになります:
// 間違った実装例
<Routes>
<PrivateRoute path="/dashboard" element={<Dashboard />} />
<Route path="/home" element={<Home />} />
</Routes>
解決策
方法1: ラッパーコンポーネントを使う(推奨)
React Router v6 の公式ドキュメントで推奨されている方法です:
// ProtectedRoute.js
import { Navigate, useLocation } from 'react-router-dom';
export const ProtectedRoute = ({ children }) => {
const isAuthenticated = isauth(); // 認証状態をチェック
const location = useLocation();
if (!isAuthenticated) {
// ログインページにリダイレクトし、元の場所を保存
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
};
// App.js
import { Routes, Route } from 'react-router-dom';
import { ProtectedRoute } from './ProtectedRoute';
function App() {
return (
<Routes>
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
<Route path="/login" element={<Login />} />
<Route path="/home" element={<Home />} />
</Routes>
);
}
方法2: Outlet を利用する
ネストされたルート構造に適した方法です:
// PrivateRoute.js
import { Navigate, Outlet } from 'react-router-dom';
const PrivateRoute = () => {
const isAuthenticated = isauth();
return isAuthenticated ? <Outlet /> : <Navigate to="/login" />;
};
// App.js
import { Routes, Route } from 'react-router-dom';
function App() {
return (
<Routes>
<Route element={<PrivateRoute />}>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
</Route>
<Route path="/login" element={<Login />} />
<Route path="/home" element={<Home />} />
</Routes>
);
}
方法3: 高階コンポーネントパターン
コンポーネントを直接ラップする方法:
// private.js
import { Navigate } from 'react-router-dom';
export const privateRoute = (Component) => {
const isAuthenticated = isauth();
return isAuthenticated ? <Component /> : <Navigate to="/login" />;
};
// routes.js
import { privateRoute } from './private';
<Routes>
<Route path="/dashboard" element={privateRoute(Dashboard)} />
<Route path="/home" element={<Home />} />
</Routes>
ベストプラクティス
TIP
- 認証状態の管理: React Context または状態管理ライブラリを使用して認証状態を一元管理する
- リダイレクト時の状態保持:
useLocation
フックで元のパスを保存し、ログイン後に戻れるようにする - ローディング状態: 非同期の認証チェック中はローディング表示を追加する
WARNING
Navigate
コンポーネントは useEffect
内ではなく、直接レンダリング結果として返す必要があります。そうしないと、React のルールに違反する可能性があります。
完全な実装例
以下はコンテキストを使用した完全な実装例です:
import { createContext, useContext, useState, useEffect } from 'react';
const AuthContext = createContext();
export const useAuth = () => {
return useContext(AuthContext);
};
export const AuthProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// 認証状態のチェック(ローカルストレージなどから)
const user = localStorage.getItem('user');
if (user) {
setCurrentUser(JSON.parse(user));
}
setLoading(false);
}, []);
const value = {
currentUser,
login: (userData) => {
localStorage.setItem('user', JSON.stringify(userData));
setCurrentUser(userData);
},
logout: () => {
localStorage.removeItem('user');
setCurrentUser(null);
}
};
return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
);
};
import { Navigate, useLocation } from 'react-router-dom';
import { useAuth } from './AuthContext';
export const ProtectedRoute = ({ children }) => {
const { currentUser } = useAuth();
const location = useLocation();
if (!currentUser) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
};
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { AuthProvider, useAuth } from './AuthContext';
import { ProtectedRoute } from './ProtectedRoute';
function AppRoutes() {
return (
<Routes>
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
<Route path="/login" element={<Login />} />
<Route path="/" element={<Home />} />
</Routes>
);
}
function App() {
return (
<AuthProvider>
<BrowserRouter>
<AppRoutes />
</BrowserRouter>
</AuthProvider>
);
}
まとめ
React Router v6 では、プライベートルートの実装方法が変更され、より宣言的で柔軟なアプローチが可能になりました。ラッパーコンポーネントパターンを使用することで、クリーンで保守性の高いコードを実現できます。認証状態の管理には React Context を活用し、ユーザーエクスペリエンスを考慮したリダイレクト処理を実装しましょう。
詳細は React Router 公式ドキュメントを参照してください。