Client-side Routing
In a traditional website, every URL change triggers a server request for a new HTML page. In a React single-page application (SPA), the JavaScript bundle loads once and routing happens entirely in the browser — React Router swaps components in and out without a full page reload. This makes navigation feel instant and preserves application state across route changes.
Why Client-side Routing
Without a routing library, every link click navigates to a new HTML file and the React app re-initializes from scratch. Client-side routing intercepts navigation events, updates the URL using the History API, and re-renders only the components that correspond to the new route — keeping global state, scroll position, and open connections intact.
React Router v6 Basics
import { BrowserRouter, Routes, Route, Link, Outlet } from "react-router-dom";
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Link to="/users">Users</Link>
</nav>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="/users" element={<UsersLayout />}>
<Route index element={<UserListPage />} />
<Route path=":userId" element={<UserDetailPage />} />
</Route>
<Route path="*" element={<NotFoundPage />} />
</Routes>
</BrowserRouter>
);
}
BrowserRouterprovides the routing context using the browser’s History APIRoutesrenders only the first matchingRouteRoute path="*"catches all unmatched paths — use it for 404 pages<Link>renders an<a>tag that navigates without a page reload
Route Parameters with useParams
// Route defined as: <Route path="/users/:userId" element={<UserDetailPage />} />
function UserDetailPage() {
const { userId } = useParams<{ userId: string }>();
const { data: user } = useFetch<User>(`/api/users/${userId}`);
return <h1>{user?.name}</h1>;
}
NOTE
useParams returns all URL parameters as strings — convert to numbers or other types before using them in API calls or comparisons.
Programmatic Navigation with useNavigate
function LoginForm() {
const navigate = useNavigate();
async function handleSubmit(credentials: Credentials) {
await login(credentials);
navigate("/dashboard", { replace: true }); // replace: true removes login from history
}
return <form onSubmit={handleSubmit}>...</form>;
}
navigate(-1) goes back one step; navigate("/path", { state: { from: "login" } }) passes state through navigation.
Nested Routes
React Router v6 uses nested <Route> elements with <Outlet> to render child routes inside a parent layout.
function UsersLayout() {
return (
<div>
<Sidebar /> {/* Always visible on /users/* routes */}
<main>
<Outlet /> {/* UserListPage or UserDetailPage renders here */}
</main>
</div>
);
}
Router Structure Diagram
graph LR Browser["Browser URL"] BrowserRouter["BrowserRouter\n(History API context)"] Routes["Routes\n(matches first Route)"] Home["/ → HomePage"] About["/about → AboutPage"] UsersLayout["/users/* → UsersLayout"] UserList["/users (index) → UserListPage"] UserDetail["/users/:id → UserDetailPage"] NotFound["* → NotFoundPage"] Browser --> BrowserRouter --> Routes Routes --> Home Routes --> About Routes --> UsersLayout Routes --> NotFound UsersLayout --> UserList UsersLayout --> UserDetail
Protected Routes
A common pattern for requiring authentication before accessing certain routes:
function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { user, isLoading } = useAuth();
const location = useLocation();
if (isLoading) return <Spinner />;
if (!user) {
// Redirect to login, preserving the attempted URL for post-login redirect
return <Navigate to="/login" state={{ from: location }} replace />;
}
return <>{children}</>;
}
// Usage
<Route
path="/dashboard"
element={
<ProtectedRoute>
<DashboardPage />
</ProtectedRoute>
}
/>
After login, retrieve the redirect target: const { state } = useLocation(); navigate(state?.from ?? "/").
TIP
If you’re using a meta-framework (Next.js, Remix, Astro), it provides its own routing system — you would not install React Router. React Router is for standalone React SPAs or Vite-based apps.
Further Learning
Search these terms to go deeper:
- “React Router v6 tutorial react.dev” — the official tutorial covering all core concepts
- “React Router useNavigate vs redirect” — when to use each approach for programmatic navigation
- “nested routes React Router v6” — layout routes, index routes, and Outlet patterns
- “TanStack Router” — a type-safe alternative to React Router with first-class TypeScript support
- “Next.js App Router vs React Router” — understanding the difference between framework routing and library routing