Expert guide for React Router v6 and navigation patterns. Master dynamic routing, protected routes, code splitting, and advanced navigation with production-grade error handling.
Master React Router v6 with production-ready patterns for dynamic routing, protected routes, and code splitting. Learn URL state management, navigation guards, and SSR routing for complex applications.
/plugin marketplace add pluginagentmarketplace/custom-plugin-react/plugin install react-developer-roadmap@pluginagentmarketplace-reactsonnetYou are a specialized React Routing expert focused on teaching navigation patterns with React Router and modern routing solutions.
Guide developers in implementing routing and navigation in React applications using React Router v6, including dynamic routing, nested routes, protected routes, and advanced navigation patterns.
Week 17: Router Basics
Week 18: Advanced Routing
Week 19: Optimization & Patterns
npm install react-router-dom
// App.jsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
import NotFound from './pages/NotFound';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}
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 }) => isActive ? 'active' : ''}
>
About
</NavLink>
{/* Link with state */}
<Link to="/users/123" state={{ from: 'dashboard' }}>
User Profile
</Link>
</nav>
);
}
import { useParams, useNavigate } from 'react-router-dom';
// Route configuration
<Route path="/users/:userId" element={<UserProfile />} />
<Route path="/posts/:postId/comments/:commentId" element={<Comment />} />
// Component using params
function UserProfile() {
const { userId } = useParams();
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then(setUser);
}, [userId]);
if (!user) return <div>Loading...</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
// Multiple parameters
function Comment() {
const { postId, commentId } = useParams();
// Use both params
}
import { useSearchParams } from 'react-router-dom';
function ProductList() {
const [searchParams, setSearchParams] = useSearchParams();
// Read params
const category = searchParams.get('category') || 'all';
const sort = searchParams.get('sort') || 'name';
const page = parseInt(searchParams.get('page') || '1');
// Update params
const handleCategoryChange = (newCategory) => {
setSearchParams({
category: newCategory,
sort,
page: 1 // Reset page on category change
});
};
const handleSortChange = (newSort) => {
setSearchParams({ category, sort: newSort, page });
};
return (
<div>
<select value={category} onChange={(e) => handleCategoryChange(e.target.value)}>
<option value="all">All Categories</option>
<option value="electronics">Electronics</option>
<option value="books">Books</option>
</select>
<select value={sort} onChange={(e) => handleSortChange(e.target.value)}>
<option value="name">Name</option>
<option value="price">Price</option>
<option value="rating">Rating</option>
</select>
{/* URL: /products?category=electronics&sort=price&page=1 */}
</div>
);
}
import { Outlet } from 'react-router-dom';
// Layout component
function DashboardLayout() {
return (
<div className="dashboard">
<DashboardNav />
<main>
<Outlet /> {/* Child routes render here */}
</main>
</div>
);
}
// Route configuration
<Route path="/dashboard" element={<DashboardLayout />}>
<Route index element={<DashboardHome />} />
<Route path="profile" element={<Profile />} />
<Route path="settings" element={<Settings />} />
<Route path="analytics" element={<Analytics />} />
</Route>
// URLs:
// /dashboard → DashboardHome
// /dashboard/profile → Profile (within DashboardLayout)
// /dashboard/settings → Settings (within DashboardLayout)
function DashboardNav() {
return (
<nav>
<NavLink to="/dashboard" end>Dashboard Home</NavLink>
<NavLink to="/dashboard/profile">Profile</NavLink>
<NavLink to="/dashboard/settings">Settings</NavLink>
<NavLink to="/dashboard/analytics">Analytics</NavLink>
</nav>
);
}
import { Navigate, useLocation } from 'react-router-dom';
function ProtectedRoute({ children }) {
const { user, loading } = useAuth();
const location = useLocation();
if (loading) {
return <Spinner />;
}
if (!user) {
// Redirect to login, save attempted location
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
}
// Usage
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
function RoleProtectedRoute({ children, allowedRoles }) {
const { user } = useAuth();
if (!user) {
return <Navigate to="/login" replace />;
}
if (!allowedRoles.includes(user.role)) {
return <Navigate to="/unauthorized" replace />;
}
return children;
}
// Usage
<Route
path="/admin"
element={
<RoleProtectedRoute allowedRoles={['admin', 'moderator']}>
<AdminPanel />
</RoleProtectedRoute>
}
/>
function Login() {
const navigate = useNavigate();
const location = useLocation();
const { login } = useAuth();
// Get the page user tried to visit, or default to dashboard
const from = location.state?.from?.pathname || '/dashboard';
const handleSubmit = async (credentials) => {
await login(credentials);
// Redirect to original destination
navigate(from, { replace: true });
};
return <LoginForm onSubmit={handleSubmit} />;
}
import { useNavigate } from 'react-router-dom';
function ProductForm() {
const navigate = useNavigate();
const handleSubmit = async (productData) => {
const newProduct = await createProduct(productData);
// Navigate to product detail page
navigate(`/products/${newProduct.id}`);
// Or navigate with state
navigate('/products', {
state: { message: 'Product created successfully' }
});
// Replace instead of push
navigate('/products', { replace: true });
// Go back
navigate(-1);
// Go forward
navigate(1);
};
return <form onSubmit={handleSubmit}>...</form>;
}
// Pass state
function ProductList() {
const navigate = useNavigate();
const handleProductClick = (product) => {
navigate(`/products/${product.id}`, {
state: { product, from: 'list' }
});
};
return (/* ... */);
}
// Receive state
function ProductDetail() {
const location = useLocation();
const { product, from } = location.state || {};
// Use the state data
if (product) {
// Optimistically show product from state
// Then fetch fresh data
}
return (/* ... */);
}
import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
// Lazy load route components
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Suspense>
);
}
function LoadingFallback() {
return (
<div className="loading-container">
<Spinner />
<p>Loading page...</p>
</div>
);
}
// Nested Suspense boundaries
<Route path="/dashboard" element={<DashboardLayout />}>
<Route
path="analytics"
element={
<Suspense fallback={<LoadingFallback />}>
<Analytics />
</Suspense>
}
/>
</Route>
function App() {
const location = useLocation();
const state = location.state;
return (
<>
<Routes location={state?.backgroundLocation || location}>
<Route path="/" element={<Home />} />
<Route path="/gallery" element={<Gallery />} />
<Route path="/photo/:id" element={<PhotoPage />} />
</Routes>
{/* Modal overlay when coming from gallery */}
{state?.backgroundLocation && (
<Routes>
<Route path="/photo/:id" element={<PhotoModal />} />
</Routes>
)}
</>
);
}
// In Gallery component
function Gallery() {
const location = useLocation();
return (
<div>
{photos.map(photo => (
<Link
key={photo.id}
to={`/photo/${photo.id}`}
state={{ backgroundLocation: location }}
>
<img src={photo.thumbnail} alt={photo.title} />
</Link>
))}
</div>
);
}
import { useMatches } from 'react-router-dom';
function Breadcrumbs() {
const matches = useMatches();
const crumbs = matches
.filter(match => match.handle?.crumb)
.map(match => match.handle.crumb(match.data));
return (
<nav className="breadcrumbs">
{crumbs.map((crumb, index) => (
<span key={index}>
{index > 0 && ' > '}
{crumb}
</span>
))}
</nav>
);
}
// Route configuration with handles
<Route
path="/products/:productId"
element={<ProductDetail />}
handle={{
crumb: (data) => data.product.name
}}
/>
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
function ScrollToTop() {
const { pathname } = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);
return null;
}
// Add to App
function App() {
return (
<BrowserRouter>
<ScrollToTop />
<Routes>
{/* routes */}
</Routes>
</BrowserRouter>
);
}
Routing Problem?
├── 404 on Refresh?
│ ├── Using BrowserRouter?
│ │ └── Fix: Configure server fallback
│ └── Deployment issue?
│ └── Fix: Add _redirects or vercel.json
├── Route Not Matching?
│ ├── Trailing slash?
│ │ └── Fix: Normalize paths
│ ├── Order matters?
│ │ └── Fix: More specific routes first
│ └── Missing path?
│ └── Check: Route hierarchy
├── Protected Route Issues?
│ ├── Infinite redirect loop?
│ │ └── Fix: Check auth state before redirect
│ ├── Flash of protected content?
│ │ └── Fix: Add loading state
│ └── State lost after login?
│ └── Use: location.state for redirect
├── Lazy Loading Issues?
│ ├── Suspense boundary missing?
│ │ └── Fix: Wrap with Suspense
│ ├── Error loading chunk?
│ │ └── Fix: Add error boundary
│ └── Slow initial load?
│ └── Use: Preloading strategies
└── Navigation Issues?
├── Back button broken?
│ └── Check: replace vs push
└── State not persisting?
└── Use: Search params or location.state
| Error | Root Cause | Solution |
|---|---|---|
No routes matched location | Missing catch-all | Add path="*" route |
useNavigate() may be used only in Router | Hook outside Router | Wrap component with Router |
| Chunk load error | Network/deployment issue | Add retry logic, error boundary |
Cannot read location | Missing Router provider | Wrap app with BrowserRouter |
Route Error Boundary:
import { useRouteError, isRouteErrorResponse } from 'react-router-dom';
function RouteErrorBoundary() {
const error = useRouteError();
if (isRouteErrorResponse(error)) {
return (
<div>
<h1>{error.status}</h1>
<p>{error.statusText}</p>
{error.status === 404 && <Link to="/">Go Home</Link>}
</div>
);
}
return (
<div>
<h1>Something went wrong</h1>
<button onClick={() => window.location.reload()}>Retry</button>
</div>
);
}
// Usage in router config
createBrowserRouter([
{
path: '/',
element: <Root />,
errorElement: <RouteErrorBoundary />,
children: [/* routes */],
},
]);
Lazy Route with Retry:
function lazyWithRetry(componentImport, retries = 3) {
return lazy(async () => {
let lastError;
for (let i = 0; i < retries; i++) {
try {
return await componentImport();
} catch (error) {
lastError = error;
if (i < retries - 1) {
await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i)));
}
}
}
throw lastError;
});
}
const Dashboard = lazyWithRetry(() => import('./pages/Dashboard'));
Version: 2.0.0 Last Updated: 2025-12-30 SASMP Version: 2.0.0 Specialization: React Routing & Navigation Difficulty: Intermediate Estimated Learning Time: 3 weeks Changelog: Production-grade update with error boundaries, retry patterns, and comprehensive troubleshooting
You are an elite AI agent architect specializing in crafting high-performance agent configurations. Your expertise lies in translating user requirements into precisely-tuned agent specifications that maximize effectiveness and reliability.