|$ curl https://forge-ai.dev/api/markdown?path=docs/react/routing
$cat docs/routing.md
updated Recently·35 min read·published

Routing

ReactRoutingNavigationIntermediate🎯Free Tools
Introduction

Routing maps URLs to components, enabling navigation between different views in a single-page application. Unlike traditional multi-page apps, React SPAs don't reload the page when navigating — the router intercepts URL changes and renders the appropriate component.

There are two main approaches: client-side routing with React Router (for SPAs), and file-based routing with Next.js/Remix (for server-rendered apps). File-based routing is now the recommended approach for new projects.

React Router v7

React Router is the standard routing library for React. Version 7 (formerly Remix) adds server-side rendering, data loading, and form handling. Here's the core API:

react-router.jsx
JSX
1import { BrowserRouter, Routes, Route, Link, NavLink, Navigate } from "react-router-dom";
2
3function App() {
4 return (
5 <BrowserRouter>
6 <Navigation />
7 <Routes>
8 <Route path="/" element={<HomePage />} />
9 <Route path="/about" element={<AboutPage />} />
10 <Route path="/users" element={<UsersLayout />}>
11 <Route index element={<UserList />} />
12 <Route path=":userId" element={<UserProfile />} />
13 <Route path=":userId/posts" element={<UserPosts />} />
14 </Route>
15 <Route path="/dashboard" element={<ProtectedRoute><Dashboard /></ProtectedRoute>} />
16 <Route path="*" element={<NotFoundPage />} />
17 </Routes>
18 </BrowserRouter>
19 );
20}
21
22function Navigation() {
23 return (
24 <nav>
25 {/* NavLink adds "active" class automatically */}
26 <NavLink to="/" className={({ isActive }) => isActive ? "nav-link active" : "nav-link"}>
27 Home
28 </NavLink>
29 <NavLink to="/about" end>About</NavLink>
30 <NavLink to="/users">Users</NavLink>
31 <NavLink to="/dashboard">Dashboard</NavLink>
32 </nav>
33 );
34}
35
36// Redirect example
37function OldRoutes() {
38 return (
39 <Routes>
40 <Route path="/old-about" element={<Navigate to="/about" replace />} />
41 <Route path="/old-blog/:slug" element={<Navigate to="/blog/:slug" replace />} />
42 </Routes>
43 );
44}
Dynamic Routes & Parameters

Dynamic route segments (prefixed with :) capture variable parts of the URL. Access them via the useParams hook. Query strings are accessed via useSearchParams.

dynamic-routes.jsx
JSX
1import { useParams, useSearchParams, useNavigate } from "react-router-dom";
2
3// Dynamic route parameter
4function UserProfile() {
5 const { userId } = useParams();
6 const { data: user, isLoading } = useQuery({
7 queryKey: ["users", userId],
8 queryFn: () => fetch(`/api/users/${userId}`).then(r => r.json()),
9 });
10
11 if (isLoading) return <Spinner />;
12 return <UserCard user={user} />;
13}
14
15// Search params (query strings)
16function ProductList() {
17 const [searchParams, setSearchParams] = useSearchParams();
18 const category = searchParams.get("category") || "all";
19 const sort = searchParams.get("sort") || "name";
20 const page = parseInt(searchParams.get("page") || "1");
21
22 const updateFilter = (key, value) => {
23 setSearchParams(prev => {
24 prev.set(key, value);
25 if (key === "category") prev.set("page", "1"); // reset page on filter change
26 return prev;
27 });
28 };
29
30 return (
31 <div>
32 <select value={category} onChange={e => updateFilter("category", e.target.value)}>
33 <option value="all">All Categories</option>
34 <option value="electronics">Electronics</option>
35 <option value="clothing">Clothing</option>
36 </select>
37 <select value={sort} onChange={e => updateFilter("sort", e.target.value)}>
38 <option value="name">Name</option>
39 <option value="price">Price</option>
40 <option value="newest">Newest</option>
41 </select>
42 {/* Product list renders here */}
43 <Pagination current={page} onChange={p => updateFilter("page", String(p))} />
44 </div>
45 );
46}
47
48// Programmatic navigation
49function SearchForm() {
50 const navigate = useNavigate();
51 const [query, setQuery] = useState("");
52
53 const handleSearch = (e) => {
54 e.preventDefault();
55 navigate(`/search?q=${encodeURIComponent(query)}`);
56 };
57
58 return (
59 <form onSubmit={handleSearch}>
60 <input value={query} onChange={e => setQuery(e.target.value)} />
61 <button type="submit">Search</button>
62 </form>
63 );
64}

info

Use useSearchParams for filter/sort/pagination state that should be shareable via URL. This lets users bookmark filtered views and share links. Keep transient UI state (modals, drawers) in component state, not the URL.
Nested Layouts

Nested routes let you share layouts across multiple pages. Parent routes render an Outlet where child routes are displayed. This avoids duplicating headers, sidebars, and navigation in every page.

nested-layouts.jsx
JSX
1import { Outlet } from "react-router-dom";
2
3// Layout with shared sidebar
4function DashboardLayout() {
5 return (
6 <div className="flex">
7 <aside className="w-64 border-r p-4">
8 <nav>
9 <NavLink to="/dashboard" end>Overview</NavLink>
10 <NavLink to="/dashboard/analytics">Analytics</NavLink>
11 <NavLink to="/dashboard/users">Users</NavLink>
12 <NavLink to="/dashboard/settings">Settings</NavLink>
13 </nav>
14 </aside>
15 <main className="flex-1 p-8">
16 {/* Child routes render here */}
17 <Outlet />
18 </main>
19 </div>
20 );
21}
22
23// Nested route definitions
24function App() {
25 return (
26 <Routes>
27 <Route element={<AuthLayout />}>
28 <Route path="/login" element={<LoginPage />} />
29 <Route path="/register" element={<RegisterPage />} />
30 </Route>
31
32 <Route element={<DashboardLayout />}>
33 <Route path="/dashboard" element={<OverviewPage />} />
34 <Route path="/dashboard/analytics" element={<AnalyticsPage />} />
35 <Route path="/dashboard/users" element={<UsersPage />}>
36 <Route path=":userId" element={<UserModal />} />
37 </Route>
38 <Route path="/dashboard/settings" element={<SettingsPage />} />
39 </Route>
40
41 <Route element={<DocsLayout />}>
42 <Route path="/docs" element={<DocsIndex />} />
43 <Route path="/docs/:category" element={<CategoryPage />} />
44 <Route path="/docs/:category/:slug" element={<DocPage />} />
45 </Route>
46 </Routes>
47 );
48}
49
50// Auth layout — wraps login/register with centered card
51function AuthLayout() {
52 return (
53 <div className="min-h-screen flex items-center justify-center bg-gray-50">
54 <div className="w-full max-w-md p-8">
55 <Outlet />
56 </div>
57 </div>
58 );
59}
60
61// Layout component with shared data
62function DocsLayout() {
63 const { data: categories } = useQuery({
64 queryKey: ["doc-categories"],
65 queryFn: fetchDocCategories,
66 });
67
68 return (
69 <div className="flex">
70 <Sidebar categories={categories} />
71 <main className="flex-1">
72 <Outlet context={{ categories }} />
73 </main>
74 </div>
75 );
76}
Route Guards & Protected Routes

Protected routes restrict access based on authentication and authorization. Wrap routes in a guard component that checks conditions before rendering the protected content.

route-guards.jsx
JSX
1import { Navigate, useLocation } from "react-router-dom";
2
3// Basic auth guard
4function ProtectedRoute({ children, requiredRole }) {
5 const { user, isAuthenticated, isLoading } = useAuth();
6 const location = useLocation();
7
8 if (isLoading) return <LoadingSpinner />;
9
10 if (!isAuthenticated) {
11 // Save intended destination for redirect after login
12 return <Navigate to="/login" state={{ from: location }} replace />;
13 }
14
15 if (requiredRole && user.role !== requiredRole) {
16 return <Navigate to="/unauthorized" replace />;
17 }
18
19 return children;
20}
21
22// Login page — redirects to intended destination after login
23function LoginPage() {
24 const { login } = useAuth();
25 const navigate = useNavigate();
26 const location = useLocation();
27 const from = location.state?.from?.pathname || "/dashboard";
28
29 const handleSubmit = async (e) => {
30 e.preventDefault();
31 try {
32 await login(email, password);
33 navigate(from, { replace: true });
34 } catch (err) {
35 setError(err.message);
36 }
37 };
38
39 return (
40 <form onSubmit={handleSubmit}>
41 {/* login form */}
42 </form>
43 );
44}
45
46// Route configuration with guards
47function AppRoutes() {
48 return (
49 <Routes>
50 {/* Public routes */}
51 <Route path="/" element={<HomePage />} />
52 <Route path="/login" element={<LoginPage />} />
53 <Route path="/register" element={<RegisterPage />} />
54
55 {/* Protected routes — require authentication */}
56 <Route element={<ProtectedRoute><DashboardLayout /></ProtectedRoute>}>
57 <Route path="/dashboard" element={<OverviewPage />} />
58 <Route path="/dashboard/analytics" element={<AnalyticsPage />} />
59 </Route>
60
61 {/* Admin-only routes */}
62 <Route element={<ProtectedRoute requiredRole="admin"><AdminLayout /></ProtectedRoute>}>
63 <Route path="/admin" element={<AdminDashboard />} />
64 <Route path="/admin/users" element={<AdminUsers />} />
65 </Route>
66
67 {/* Fallback */}
68 <Route path="*" element={<NotFoundPage />} />
69 </Routes>
70 );
71}

warning

Never rely solely on client-side route guards for security. Always validate authentication and authorization on the server too. Client-side guards improve UX by preventing flash of unauthorized content, but they can be bypassed.
File-Based Routing (Next.js)

File-based routing (used by Next.js, Remix, and Nuxt) generates routes automatically from your file system structure. Each file in the app/ directory becomes a route. This eliminates manual route configuration.

nextjs-file-routing.txt
TEXT
1app/
2 layout.tsx → Root layout (wraps all pages)
3 page.tsx → Route: /
4 loading.tsx → Loading UI for all routes
5 error.tsx → Error boundary for all routes
6
7 about/
8 page.tsx → Route: /about
9
10 blog/
11 page.tsx → Route: /blog
12 [slug]/
13 page.tsx → Route: /blog/:slug
14 loading.tsx → Loading state for blog posts
15
16 dashboard/
17 layout.tsx → Dashboard layout (sidebar + outlet)
18 page.tsx → Route: /dashboard
19 analytics/
20 page.tsx → Route: /dashboard/analytics
21 users/
22 page.tsx → Route: /dashboard/users
23 [userId]/
24 page.tsx → Route: /dashboard/users/:userId
25
26 api/
27 users/
28 route.ts → API route: /api/users
nextjs-routing.jsx
JSX
1// app/layout.tsx — wraps every page
2export default function RootLayout({ children }) {
3 return (
4 <html lang="en">
5 <body>
6 <Header />
7 {children}
8 <Footer />
9 </body>
10 </html>
11 );
12}
13
14// app/dashboard/layout.tsx — wraps dashboard pages
15export default function DashboardLayout({ children }) {
16 return (
17 <div className="flex">
18 <Sidebar />
19 <main className="flex-1">{children}</main>
20 </div>
21 );
22}
23
24// app/dashboard/users/[userId]/page.tsx — dynamic route
25export default async function UserPage({ params }) {
26 const { userId } = await params;
27 const user = await fetchUser(userId);
28
29 return (
30 <div>
31 <h1>{user.name}</h1>
32 <p>{user.email}</p>
33 </div>
34 );
35}
36
37// generateStaticParams for static generation
38export async function generateStaticParams() {
39 const users = await fetchAllUsers();
40 return users.map(user => ({ userId: user.id.toString() }));
41}

best practice

File-based routing is the standard for new React projects. Next.js App Router with React Server Components gives you the best combination of performance, developer experience, and production features. Use React Router only when you can't use a framework.