Topic 13.2
React Router Basics
In one line
React Router maps URLs to components: <Routes> picks the best <Route> for the current URL, <Link> changes the URL without reloading, and <NavLink> knows when it points at the current page; apps wrap everything in <BrowserRouter>, while tests and this preview use <MemoryRouter>.
Think of it like this
A hotel receptionist with a list of room numbers. A guest says a room number (the URL), the receptionist looks at the list (the routes), and sends the guest to the right room (the component). If nobody has that number, the guest is sent to the "sorry, no such room" desk (a not-found route). The receptionist always looks for the best match on the whole list, not the first line that roughly fits.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- React Router
- The most used routing library for React. Version 8 is imported from the single package
react-router. - BrowserRouter
- The router for real apps. It reads and writes the browser's address bar with the History API.
- MemoryRouter
- A router that keeps its history in memory. You give it a start URL with
initialEntries. - Routes
- The component that compares the current URL with its
<Route>children and renders the best match. - Route
- One rule: a
pathpattern and theelementto show when it matches. - Link
- A router-aware
<a>: changes the URL without reloading the page. - NavLink
- A
Linkthat knows when it points at the current page and marks itselfactivewitharia-current="page". - Active link
- A navigation link whose target matches the current URL, usually styled to show "you are here".
- Library mode
- How you use React Router: declarative (components), data (route objects with loaders) or framework (Vite plugin, files, server rendering).
Step by step
01Install and wrap the app
In a real Vite app, install the package and put <BrowserRouter> around <App /> once, in the file that mounts React. Every component inside can now use links, routes and router hooks.
// npm i react-router
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter } from "react-router";
import App from "./App.jsx";
createRoot(document.getElementById("root")).render(
<StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</StrictMode>,
);02Declare the routes
In App, list the pages. Anything outside <Routes> (the header, the nav) shows on every page; only the matching route's element changes.
import { Routes, Route, NavLink } from "react-router";
import Home from "./pages/Home.jsx";
import About from "./pages/About.jsx";
export default function App() {
return (
<>
<nav>
<NavLink to="/">Home</NavLink>
<NavLink to="/about">About</NavLink>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</>
);
}03How a click becomes a new screen
Link cancels the browser's navigation, the router pushes the new location, and every component that reads the location (here <Routes> and each NavLink) re-renders. The header and nav components stay mounted, so their state survives.
04In the preview: MemoryRouter
The live examples swap <BrowserRouter> for <MemoryRouter initialEntries={["/"]}> and show useLocation().pathname in a small address bar. Everything else is identical, which is the point: your routes don't know which router they live in.
<MemoryRouter initialEntries={["/blog/hello"]}>
<App />
</MemoryRouter>05Style the active link
Use the active class or a className function. Prefer styling [aria-current="page"] in CSS: the same attribute that helps screen readers then drives the visual style, so the two can't disagree.
nav a { color: #495057; text-decoration: none; }
nav a[aria-current="page"] { color: #1971c2; font-weight: 700; border-bottom: 2px solid currentColor; }06Know the three modes
The same Link, NavLink, Outlet and hooks work in all modes. What changes is where routes are defined and who loads data.
Try it yourself
- 1
Add a Contact page
In the first example, add a
Contactcomponent, a<Route path="/contact">and a<Link to="/contact">. ChangeinitialEntriesto["/contact"]. Predict what the page shows on load before you run it. - 2
Remove end
In the NavLink example, remove
endfrom "Blog home". Predict its class andaria-currenton/blog/hello, then run. Then changeinitialEntriesto["/"]: which links are active now? - 3
Test the ranking
In the ranking example, add
"/teams/42/edit"tourls. Which route matches? (Hint::teamIdmatches exactly one segment.) Add a route that handles it.
Code & diagrams
The plain `<a>` is ignored by the router. In a real browser it would reload the whole page from the server; this preview only logs `(link to /about)`. `<Link>` changes the route in place.
Preview
Console
Nothing logged yet.Expected console output
plain <a> -> / Home
<Link> -> /about About usOn `/blog/hello`: Home is not active (`/` is special), "Blog home" isn't either because of `end`, "Blog section" is (it matches the start of the path), and the function `className` replaced the default `active` class.
Preview
Console
Nothing logged yet.Expected console output
Home: class= aria-current=null
Blog home: class= aria-current=null
Blog section: class=active aria-current=page
Hello post: class=tab tab-on aria-current=pageFour separate memory routers, one per URL. Even with `*` written first, `/teams/new` picks the static route and `/teams/42` the dynamic one. `*` only wins when nothing else matches.
Preview
Console
Nothing logged yet.Expected console output
/teams matched All teams
/teams/new matched New team form (static)
/teams/42 matched One team (:teamId)
/players matched Not found (*)// v6 (two packages)
import { BrowserRouter, Link } from "react-router-dom";
// v7 (react-router-dom still works, but only re-exports react-router)
// v8: react-router-dom is gone. One package:
import { BrowserRouter, Routes, Route, Link, NavLink } from "react-router";
// Data mode in the DOM: RouterProvider also exists in "react-router/dom"
// (that version wires in react-dom's flushSync for you)
import { RouterProvider } from "react-router/dom";Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Use a hook outside the router
Move <AddressBar /> out of <MemoryRouter> in the first example (render it above the router).
Break #2
Import from react-router-dom in v8
Copy a v6 tutorial: import { BrowserRouter } from "react-router-dom"; in a project that has only react-router 8 installed.
Myth vs fact
Myth
Routes are matched top to bottom, so order matters.
Fact
React Router ranks all routes and picks the most specific match. Static segments beat :params, which beat *. Order only matters for readability.
Myth
Link is a button that calls navigate.
Fact
Link renders a real <a href>, so it works with the keyboard, screen readers, "copy link" and "open in new tab". Use buttons for actions, links for navigation.
Myth
You need react-router-dom for web apps.
Fact
Since v7 the DOM parts live in react-router, and v8 removed react-router-dom entirely.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Version 7 removed the
v7_startTransitionfuture flag: router state updates are wrapped inReact.startTransitionby default.<BrowserRouter useTransitions={false}>turns that off. This matters when a route suspends (Topic 14.3): the old screen stays visible instead of a fallback flashing. - ▸
Linkonly intercepts "plain" left clicks: it lets the browser handle Ctrl/Cmd/Shift/Alt clicks, middle clicks and links withtarget="_blank"orreloadDocument, so native behaviours keep working. If you build your own link withuseLinkClickHandler, you get the same rules. - ▸
<Routes>can take alocationprop to render routes for a different location than the URL, which is how "modal routes" keep the previous page rendered behind a dialog (passstate: { backgroundLocation }in the link). - ▸
With
basename="/app"on the router, everytoand every matched path are relative to/app, so the same routes work when the SPA is deployed under a sub-path.
Remember this
- 1
React Router is a library, installed with
npm i react-router. Version 8 is current (8.4 in this course). Everything is imported from one package,"react-router". In version 6 the browser parts lived in a second package,react-router-dom; version 7 merged them intoreact-routerand keptreact-router-domonly as a re-export for old code; version 8 removedreact-router-domcompletely. If you seeimport { Link } from "react-router-dom"in a tutorial, it is v6/v7 code: change the package name. Version 8 also needs React 19.2.7 or newer and ships as ES modules only. - 2
A router component owns the current location and gives it to everything inside.
<BrowserRouter>uses the real address bar through the History API (Topic 13.1): you put it once around your app inmain.jsx.<MemoryRouter>keeps the history in an array in memory; you pass the starting URL withinitialEntries={["/about"]}. It is used in tests (Phase 17), in React Native-like environments, and in the live examples here, because the preview frame can't change its own URL.<HashRouter>stores the path after a#and is only for hosts where you can't add an SPA fallback. All three give the same hooks and components to your code, so the routes you write work under any of them. - 3
**
<Routes>and<Route>.** Inside the router,<Routes>holds a list of<Route path="..." element={<Page />} />. On every render,<Routes>looks at the current URL, ranks every route and renders theelementof the best match only. Ranking means static segments (/teams/new) beat dynamic ones (/teams/:teamId, Topic 13.3), which beat the catch-all*; the order you write them in doesn't matter. If nothing matches,<Routes>renders nothing at all, which is why you add apath="*"route for a not-found page (Topic 13.4). Paths are matched without regard to letter case unless you addcaseSensitive. - 4
**
<Link to="/about">** renders a real<a href="/about">, so it is a proper link for screen readers, keyboard users, right-click "copy link" and Ctrl/Cmd+click "open in new tab". On a normal click it callspreventDefault()and tells the router to navigate, which updates the URL and re-renders<Routes>, keeping the rest of the page and its state. A plain<a href>inside a React Router app does a full page load (or, in this preview, nothing). Useful props:replace(replace the current history entry instead of adding one),state(an invisible object attached to the navigation, read withuseLocation().state),reloadDocument(force a real page load), andpreventScrollReset(data routers only, Topic 13.6). - 5
**
<NavLink>** is aLinkthat knows whether it is active, meaning itstomatches the current URL. By default it addsclass="active"andaria-current="page"when active, so you can style it with CSS (nav a.active { ... }) and screen readers announce "current page".className,styleandchildrencan be functions that receive{ isActive, isPending, isTransitioning }(isPendingworks with data routers, Topic 13.5). A NavLink to/blogis also active on/blog/hello, because it matches the start of the path; add the **end** prop to make it active only on exactly/blog. The link to/is special: it is only active on/itself, otherwise it would be active everywhere. - 6
Library modes. React Router can be used in three ways, each adding features on top of the last. Declarative mode:
<BrowserRouter>+<Routes>/<Route>+Link/NavLink+ hooks (this topic and 13.3-13.4); you fetch data yourself. Data mode: routes are defined as objects outside React withcreateBrowserRouterand rendered with<RouterProvider>, and each route can have aloader, anactionand an error boundary (Topic 13.5). Framework mode: React Router's Vite plugin adds file-based route modules, server rendering, code splitting and type-safe routes (the successor of the Remix framework, briefly in Topic 13.5). Pick declarative for small apps or when another library owns your data, data mode for loaders and actions in an SPA, framework mode for a full app.
Explain it without notes
What does <BrowserRouter> do, and why do tests and this preview use <MemoryRouter>?
How does <Routes> decide which <Route> to render?
What is the difference between Link, NavLink and a plain <a> in a React Router app?
Describe React Router's three modes and when you'd pick each.
Practice
Build a three-page site (Home, Menu, Contact) with a NavLink nav where the current page is bold, inside a MemoryRouter that starts on /menu.
Make a NavLink whose text shows "(you are here)" when active, using the children function.
Rewrite this v6 code for React Router 8: import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
Trade-offs
- ↔
Declarative mode is the simplest to learn and keeps routes next to your JSX, but each page fetches its own data after it renders, which can create loading waterfalls. Data mode fixes that at the cost of defining routes outside React.
- ↔
NavLinkwithoutendhighlights a whole section (good for top-level menus), withendonly the exact page (good for tabs). Choose per menu, not globally.
Done when you can
I can set up React Router 8 with
BrowserRouterand import everything fromreact-router.I can declare pages with
RoutesandRouteand explain how the best match is chosen.I use
Linkfor navigation and know why a plain<a>reloads the page.I can style the active
NavLinkand explainendandaria-current.I can name the three library modes and what each adds.
I can use
MemoryRouterwithinitialEntriesfor tests and previews.