Chapter 14: Routing with React Router v6+
In Single Page Applications (SPAs), users expect the fluidity of a desktop app combined with standard web capabilities: shareable URLs, browser back/forward button navigation, and deep-linking. Learn how React Router v6+ facilitates client-side routing, nested layouts, and protected routes.
1. How Client-Side Routing Works (HTML5 History API)
In traditional multi-page web applications, clicking an anchor tag (<a href="/about">) instructs the browser to tear down the entire page, make an HTTP request to the server, and download a brand-new HTML document.
In contrast, React Router leverages the browser's native HTML5 History API (window.history.pushState). When a user clicks a link:
- The URL in the address bar updates immediately without a network round-trip.
- React Router listens to the location change event (
popstate). - React unmounts the old view component and mounts the new view component into the DOM in milliseconds.
2. Installation & Core Components
Install the official router package for web applications:
npm install react-router-dom
Basic Router Configuration:
import { BrowserRouter, Routes, Route, Link, NavLink } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
{/* Global Navigation Header */}
<nav className="navbar">
<NavLink to="/" className={({ isActive }) => isActive ? "nav-active" : "nav-link"}>
Home
</NavLink>
<NavLink to="/catalog" className={({ isActive }) => isActive ? "nav-active" : "nav-link"}>
Catalog
</NavLink>
</nav>
{/* Route Switchboard */}
<main className="page-content">
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/catalog" element={<CatalogPage />} />
<Route path="/catalog/:productId" element={<ProductDetailsPage />} />
{/* Wildcard 404 Catch-All */}
<Route path="*" element={<NotFoundPage />} />
</Routes>
</main>
</BrowserRouter>
);
}3. Dynamic Route Parameters & Query Strings
`useParams` (Dynamic URL Segments)
Extracts variables declared with a colon in the route path (e.g., /catalog/:productId):
import { useParams } from 'react-router-dom';
function ProductDetailsPage() {
// If URL is /catalog/shoes-99:
const { productId } = useParams();
return <h2>Viewing Product: {productId}</h2>;
}`useSearchParams` (Query Strings)
Reads and updates query parameters (e.g., /search?q=react&page=2):
import { useSearchParams } from 'react-router-dom';
function SearchPage() {
const [searchParams, setSearchParams] = useSearchParams();
const query = searchParams.get('q') || '';
return (
<input
value={query}
onChange={(e) => setSearchParams({ q: e.target.value })}
placeholder="Filter products..."
/>
);
}4. Nested Routes & The `<Outlet />` Component
In large dashboards, navigation sidebars and top headers should remain persistent while only the main inner panel changes. React Router v6 implements this through nested routes and the <Outlet /> component:
// 1. Dashboard Layout Shell
import { Outlet, NavLink } from 'react-router-dom';
function DashboardLayout() {
return (
<div className="dashboard-grid">
<aside className="sidebar">
<NavLink to="/dashboard/analytics">Analytics</NavLink>
<NavLink to="/dashboard/settings">Settings</NavLink>
</aside>
<section className="main-panel">
{/* The matching child route is rendered right here! */}
<Outlet />
</section>
</div>
);
}
// 2. Defining Nested Route Hierarchy:
<Route path="/dashboard" element={<DashboardLayout />}>
<Route path="analytics" element={<AnalyticsView />} />
<Route path="settings" element={<SettingsView />} />
</Route>5. Protected Routes & Authentication Guards
To protect restricted screens (like user profiles or payment portals), create an authentication guard wrapper:
import { Navigate, Outlet } from 'react-router-dom';
function ProtectedRoute({ isAuthenticated }) {
if (!isAuthenticated) {
// Redirect unauthenticated user to login screen
return <Navigate to="/login" replace />;
}
// Render child routes if authenticated
return <Outlet />;
}
// In App Router:
<Routes>
<Route path="/login" element={<LoginPage />} />
{/* Guarded Routes */}
<Route element={<ProtectedRoute isAuthenticated={userToken !== null} />}>
<Route path="/account" element={<AccountPage />} />
<Route path="/orders" element={<OrdersPage />} />
</Route>
</Routes>Frequently Asked Questions (FAQ)
How does client-side routing in React differ from traditional server routing?
In traditional server routing, clicking a link sends a fresh HTTP request to the web server, which sends back an entirely new HTML document, causing a full-page reload and flashing screen. In client-side routing (React Router), the browser's HTML5 History API (pushState) updates the URL path while React intercepts the navigation, swapping out only the necessary view components in memory instantly without a full page refresh.
What is the purpose of the <Outlet /> component in React Router v6?
<Outlet /> is a placeholder component used inside parent layout routes. When a child route matches the current URL, React Router renders that child route's component inside the parent's <Outlet /> slot, enabling persistent shared layouts (like sidebars and navigation headers).
What is the difference between <Link> and <NavLink>?
<Link> creates an accessible HTML anchor tag that prevents browser page reloads and navigates to the target URL. <NavLink> is a specialized version of <Link> that automatically applies an 'active' CSS class or provides an isActive boolean prop when its href matches the current browser location, ideal for navigation menus.
How do you implement a 404 Not Found page in React Router v6?
Add a Route at the very end of your <Routes> definition with the wildcard path path='*' (e.g., <Route path='*' element={<NotFoundPage />} />). React Router will render this component whenever no other declared route matches the current URL.
Explore Related Tools
Hand-picked utilities and calculators related to this tool.
