React Router Setup
React by itself has no concept of pages or URLs — every app you’ve built so far renders one component tree at one URL. React Router is the standard library that adds client-side routing: it lets you map different URL paths to different components, so your app can feel like a multi-page site while staying a single-page application (SPA) that never triggers a full browser reload. This lesson covers installing React Router, wiring up the core components, and rendering your first routes.
Overview: How React Router Works
A traditional multi-page website asks the server for a new HTML document every time the URL changes. A React SPA instead loads one HTML page and one JavaScript bundle, and then manages everything else — including the URL — on the client. React Router listens for changes to the browser’s URL (via the History API) and, instead of letting the browser fetch a new page, it swaps out which React components are rendered to match the new path. The address bar updates, the back/forward buttons work, and bookmarked URLs still work — but no network round trip happens for the page itself.
React Router v6+ (the current major version) is built around three ideas:
- A router component (most commonly
BrowserRouter) that uses the HTML5 History API to keep the UI in sync with the URL. It must wrap any part of your app that uses routing features. RoutesandRoute, which declare which component should render for which path.Routeslooks at the current URL and renders the single best-matchingRoute‘selement.LinkandNavLink, which render anchor tags that change the URL through React Router (updating state and callinghistory.pushStateunder the hood) instead of asking the browser to navigate away and reload.
Because routing state lives in the URL, and React re-renders whenever the matched route changes, navigating your app is just another form of the same render cycle you already know: URL changes → Routes re-evaluates which Route matches → React renders the new element → React reconciles the tree and commits only the DOM differences.
Installing React Router
React Router is a separate package, not part of core React. Install it with npm or yarn in your project directory:
npm install react-router-dom
You import routing components from react-router-dom (the DOM-flavored package, as opposed to react-router-native for React Native).
Syntax
The minimal setup has three layers: a router at the root, a Routes block that lists possible paths, and Route elements that pair a path with an element to render.
import { BrowserRouter, Routes, Route } from "react-router-dom";
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
);
}
| Part | Purpose |
|---|---|
BrowserRouter |
Provides routing context to the whole tree; keeps the UI synced with the URL via the History API. Render it once, near the root. |
Routes |
A container that inspects the current URL and renders exactly one matching child Route‘s element. |
Route |
Declares a path to match and the element to render when it matches. |
path |
A URL pattern, e.g. /, /about, or /users/:id (dynamic segments use a leading colon). |
element |
A JSX element (not a bare component reference) to render for that route, e.g. element={<About />}. |
Link |
Renders an <a> that navigates via React Router instead of a full page load. Takes a to prop instead of href. |
Examples
Example 1: A basic two-page app
This is the smallest complete setup: an entry file that mounts the app, and an App component that defines two routes and a nav bar to move between them.
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.jsx";
createRoot(document.getElementById("root")).render(
<StrictMode>
<App />
</StrictMode>
);
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
function Home() {
return <h1>Home Page</h1>;
}
function About() {
return <h1>About Page</h1>;
}
function NotFound() {
return <h1>404: Page Not Found</h1>;
}
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}
export default App;
Visiting / renders “Home Page”, visiting /about renders “About Page”, and visiting anything else (like /xyz) matches the catch-all path="*" route and renders “404: Page Not Found”. Clicking the Link elements changes the URL and swaps the rendered page without a full browser reload.
Example 2: Highlighting the active link with NavLink
NavLink works like Link but knows when its to path matches the current URL, letting you style the active link differently.
import { NavLink } from "react-router-dom";
function NavBar() {
return (
<nav>
<NavLink
to="/"
className={({ isActive }) => (isActive ? "link active" : "link")}
>
Home
</NavLink>
<NavLink
to="/about"
className={({ isActive }) => (isActive ? "link active" : "link")}
>
About
</NavLink>
</nav>
);
}
export default NavBar;
Renders two navigation links. Whichever one matches the current URL receives the "active" class in addition to "link", so you can style it (e.g. underline or bold it) in your CSS. NavLink‘s className prop accepts a function that receives { isActive }, computed automatically by comparing the link’s to path against the current location.
Example 3: A shared layout with Outlet
Real apps usually want a persistent layout (header, footer) around every page. Nesting a parent Route with child routes and an Outlet placeholder achieves this without repeating the layout in every page component.
import { BrowserRouter, Routes, Route, Outlet, Link } from "react-router-dom";
function Layout() {
return (
<div>
<header>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</header>
<main>
<Outlet />
</main>
<footer>© 2026 My Site</footer>
</div>
);
}
function Home() {
return <p>Welcome home!</p>;
}
function About() {
return <p>About us.</p>;
}
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="about" element={<About />} />
</Route>
</Routes>
</BrowserRouter>
);
}
export default App;
The header and footer render for every page because Layout is the parent route’s element. Outlet is where React Router injects whichever child route matched — Home for the index route (the default child at /) or About for /about. This nested pattern is covered in depth in the “Nested Routes” lesson; here it’s shown to demonstrate that setup scales beyond flat route lists.
Under the Hood: What Happens on Navigation
Understanding the sequence helps you reason about routing bugs:
- On initial load,
BrowserRouterreadswindow.locationonce and stores the current path in its internal state, then provides it via context to every descendant. - On mount,
Routesreads that context, compares the current path against each childRoute‘spath, and renders the single best match’selement. - On a
Linkclick, React Router callsevent.preventDefault()on the anchor’s click, then callshistory.pushStateto update the URL without a network request, and updates its internal location state. - On that state update,
BrowserRouterre-renders, the new location flows down through context,Routesre-evaluates the match, and React renders (and reconciles/commits) whatever newelementis now the best match — unmounting the old page’s component and mounting the new one. - On browser back/forward, the same thing happens, but triggered by the browser’s
popstateevent instead of aLinkclick.
Common Mistakes
Mistake 1: Using Routes without a router
Forgetting to wrap Routes in a BrowserRouter throws an error because Routes needs routing context to know the current location.
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
</Routes>
);
}
This throws: “useLocation() may be used only in the context of a <Router> component.” Fix it by wrapping the routes in BrowserRouter (usually once, near the top of the app):
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
</Routes>
</BrowserRouter>
);
}
Mistake 2: Using a plain <a> tag for internal navigation
A regular anchor tag forces a full page reload, throwing away all React state and re-downloading the entire JS bundle — defeating the purpose of an SPA.
function NavBar() {
return <a href="/about">About</a>;
}
Use Link (or NavLink) instead, which intercepts the click and navigates client-side:
import { Link } from "react-router-dom";
function NavBar() {
return <Link to="/about">About</Link>;
}
Mistake 3: Putting the catch-all route first
Since React Router v6 picks the best match rather than the first match, order matters less than in v5 — but a wildcard listed before specific routes can still be confusing to read and is best kept last by convention, as shown in Example 1’s path="*" route appearing after the named routes.
Best Practices
- Install and import from
react-router-dom(notreact-router) for web apps. - Render
BrowserRouteronce, as close to the root of your app as practical (often directly insideAppor around it in your entry file). - Always use
Link/NavLinkfor internal navigation, never a plain<a href>, so the SPA doesn’t reload. - Pass a JSX element to
element(e.g.element={<About />}), not a bare component reference (element={About}), so React Router can render it with any props you supply. - Add a catch-all
<Route path="*" element={<NotFound />} />as the last route so unmatched URLs show a friendly 404 instead of a blank page. - Use
NavLinkinstead ofLinkwhenever you need to style the currently active navigation item. - Keep route definitions in one place (often
App.jsx) so the app’s URL structure is easy to see at a glance.
Practice Exercises
- Exercise 1: Create a new React app, install
react-router-dom, and set up three routes:/,/contact, and a catch-all 404 route. Add a nav bar withLinks to the first two. - Exercise 2: Convert your nav bar to use
NavLinkand add CSS so the active link appears bold or underlined. - Exercise 3: Add a shared
Layoutcomponent with a header and footer, and nest yourHomeandContactroutes inside it usingOutlet, so the header/footer appear on every page without being repeated in each page component.
Summary
- React Router adds client-side routing to React, mapping URLs to components without full page reloads.
- Install it with
npm install react-router-domand import from that package. BrowserRoutermust wrap any part of the app using routing features; it syncs the UI with the browser’s URL via the History API.Routesrenders the single best-matching childRoute‘selementfor the current URL.- Use
LinkandNavLinkinstead of plain anchor tags for internal navigation so the app stays a true SPA. Outletlets nested routes share a parent layout, rendering the matched child route in place.- A catch-all
path="*"route at the end of your route list handles unmatched URLs gracefully.
