Routing
A React app renders one component tree into one HTML document. Real sites have a home page, an about page, a details page, each with its own URL. Routing is how those two facts meet: the URL decides which components render, and clicking around the app updates the URL. React Router is the library most React apps use for this, and the course dedicates a whole section to it, building a van rental app called VanLife along the way. This chapter covers the core pieces; nested routes and route params build on them.
Client-side routing: MPA vs SPA
In a traditional multi-page application (MPA), every navigation is a round trip. The browser requests /about from the server, the server assembles an HTML page and sends it back, and the browser throws away the current page to load the new one. That full-page swap is visible as a telltale flicker on every click.
A single-page application (SPA) loads one document once. The name is a little misleading: the site can still have many pages in the user's eyes. What is single is the document. After the first load, the React app itself decides what to show for each URL. Navigating to /about swaps components inside the running app, with no new document requested and no flicker. If a view needs fresh data, the app fetches JSON in the background and updates in place, the pattern covered in Fetching data.
Client-side routing is the mechanism that makes this work: a library watches the URL, intercepts navigation, and renders the matching components instead of letting the browser reload. There is a real prize for keeping the document alive. A full page load wipes all React state; staying inside one document means your state survives every navigation.
Version note
The course teaches React Router 6. Version 7 merged React Router with Remix and consolidated the packages, so react-router is the current import path. react-router-dom survives in v7 as a re-export and is the package v6 and the course use, which is why every example in this handbook imports from it; version 8 dropped it, so a fresh install imports these components from react-router.
React Router documents three modes: declarative, the BrowserRouter and Route components this chapter covers; data, which pairs createBrowserRouter with loaders and actions; and framework, a Vite plugin that adds typed route modules, code splitting, and server rendering. The component and hook names in this chapter are unchanged across those versions.
BrowserRouter, Routes, and Route
Three components define a basic setup: BrowserRouter wraps the app and enables routing, Routes holds your route definitions, and each Route maps one path to one element.
import { BrowserRouter, Routes, Route } from 'react-router-dom'
export default function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
)
}Under the hood, BrowserRouter is a context provider. Wrapping the app with it gives every component below access to the router's tools, which is why it sits at the top. Some codebases rename it on import (import { BrowserRouter as Router }) since the full name is a mouthful.
Each Route takes two props. path is the part of the URL after the domain: / for the home page, /about for the about page. element is the JSX to render when the URL matches that path, written as an actual element, <Home /> rather than Home. When the URL is /about, React Router renders <About /> where the Routes component sits; everything outside Routes renders on every page.
Visit a path with no matching route and nothing renders at all. That gap gets fixed by the catch-all route below.
Link: navigating without a page load
The HTML way to move between pages is an anchor tag, and it is exactly wrong for a SPA: clicking an <a href="/about"> triggers a full page load, which discards the running app and every piece of state in it. React Router's answer is the Link component.
import { Link } from 'react-router-dom'
function Header() {
return (
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
)
}Link takes a to prop instead of href, and it reads like English: a link to the following route. In the browser it still renders a real anchor tag, so screen readers, right-click menus, and your CSS element selectors all see an ordinary <a>. The difference is the click handler: React Router intercepts the click, updates the URL, and swaps the rendered components, all without reloading the document. A counter sitting at ten stays at ten as you move between pages. Inside a React Router app, internal navigation always goes through Link; plain anchors are for external URLs.
NavLink and active link styling
Navigation bars usually highlight the page you are currently on. NavLink exists for exactly this: it behaves like Link, except its className and style props can take a function instead of a plain value. React Router calls that function with an object containing an isActive boolean, true when the link's route matches the current URL. This is the render props pattern, covered in Render props, applied to a prop other than children.
import { NavLink } from 'react-router-dom'
function Header() {
return (
<nav>
<NavLink
to="/about"
className={({ isActive }) => isActive ? 'active-link' : ''}
>
About
</NavLink>
</nav>
)
}Whatever the function returns becomes the class name, so the active-link class applies only while /about is the current route, and your CSS handles the rest. The style prop works the same way with an inline style object:
const activeStyles = { fontWeight: 'bold', textDecoration: 'underline' }
<NavLink
to="/about"
style={({ isActive }) => isActive ? activeStyles : null}
>
About
</NavLink>Class name or inline style is a team convention call; both are fully supported, so follow whatever the project already does.
A catch-all 404 route
Users land on paths that don't exist: an outdated link, a typo, someone else's broken reference. Without a fallback, React Router renders nothing there. The fix is a catch-all route, sometimes called a splat route, whose path is a single asterisk.
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="*" element={<NotFound />} />
</Routes>path="*" matches anything no other route claims, much like a universal selector in CSS. A typical NotFound component pairs a short message with a Link back to the home page. Order in the list doesn't matter: React Router 6 scores every route against the URL and picks the best match, so the catch-all only wins when nothing more specific does. Putting it last is convention and readability rather than necessity.
Wrap your app in BrowserRouter, list your pages as Route components inside Routes, and each path gets its own element to render.
Use Link instead of an anchor tag so clicking keeps the app running, and add a path="*" route so visitors who mistype a URL see a friendly page instead of a blank screen.
Next up: Nested routes and layouts, where routes gain shared chrome and routes inside routes.

