Any multi-page app needs URLs that reflect what's on screen; React itself doesn't include a router.
React has no built-in router; the most widely used is React Router (v7, imported from react-router). It can run as a plain library in a Vite app or as a full framework with loaders and SSR. The library usage looks like this:
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="users/:userId" element={<User />} />
<Route path="*" element={<NotFound />} />
</Route>
</Routes>
</BrowserRouter>Key ideas:
- Routes match the URL and render their
element. Paths can contain dynamic segments (:userId), read withuseParams(). - Nested routes build layouts. A parent route renders shared UI (header, sidebar) and an
<Outlet />where the matching child appears.indexmarks the child shown at the parent's own path. - Navigate without reloading using
<Link to="/users/42">or<NavLink>(which knows when it's active). Plain<a href>triggers a full page load. - Programmatic navigation after an action:
const navigate = useNavigate(); navigate("/done"). - Query strings via
useSearchParams(), which is a good home for filter and sort state because it survives reloads and can be shared as a link.
React Router's data mode (createBrowserRouter with loader and action per route) and framework mode go further: data loads in parallel with code before rendering, avoiding effect waterfalls. Next.js and TanStack Router are popular alternatives with file-based or type-safe routing.
On a static host, configure the server to serve index.html for unknown paths, or direct links to /users/42 will 404.
Example
import { BrowserRouter, Routes, Route, Link, NavLink, Outlet, useParams, useNavigate } from "react-router";
function Layout() {
return (
<>
<nav>
<NavLink to="/">Home</NavLink> | <NavLink to="/users/1">User 1</NavLink>
</nav>
<main><Outlet /></main> {/* matched child route renders here */}
</>
);
}
function Home() {
return <p>Welcome. See <Link to="/users/2">user 2</Link>.</p>;
}
function User() {
const { userId } = useParams();
const navigate = useNavigate();
return <><h2>User {userId}</h2><button onClick={() => navigate("/")}>Back home</button></>;
}
export default function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="users/:userId" element={<User />} />
<Route path="*" element={<p>Not found</p>} />
</Route>
</Routes>
</BrowserRouter>
);
}Remember
- Routes map URL patterns to elements;
:paramsegments are read withuseParams - Nested routes plus
<Outlet />give shared layouts - Use
<Link>/useNavigatefor client-side navigation; keep shareable UI state in the URL
Watch out for
- Using
<a href>for internal links, which reloads the whole app and loses state.