Routing with React Router
Pages, links, nested layouts, URL params, search params, loaders and 404s.
So far each app has been a single screen. Real apps have many pages — a home page, a course list, a lesson page, settings — each with its own URL that users can bookmark and share. React Router is the most popular library for this. It maps URLs to components, handles links and the back button, and can even load data before a page renders.
This lesson uses React Router v8. If you read older tutorials: the
react-router-dompackage is gone — import fromreact-router(andRouterProviderfromreact-router/dom).
Installing#
Your first router#
Create a router once, outside your components, and render it with RouterProvider:
Visit / and you see Home; visit /about and you see About. (You can also write element: <Home /> instead of Component: Home.)
Links and navigation#
Use Link for in-app links. It updates the URL without reloading the page:
NavLink knows whether it matches the current URL. It adds an active class by default (and aria-current="page" for screen readers), or you can pass a function to className/style.
Layouts with nested routes and <Outlet />#
Most pages share a header and footer. Put them in a layout route and nest pages inside it:
index: truemarks the default child shown at the parent's exact URL.- Child paths are relative —
aboutunder/becomes/about. *matches anything not matched elsewhere, perfect for a 404 page.
URL parameters with useParams#
A segment starting with : is a dynamic parameter:
Params are always strings — convert with Number(id) if you need a number.
Search params with useSearchParams#
Query strings (/courses?level=beginner&q=hooks) are great for filters, because the state lives in the URL and survives refreshes and sharing:
Navigating from code with useNavigate#
After a form submits or a login succeeds, redirect with useNavigate:
Loading data with loaders#
React Router can fetch data before rendering a route. Add a loader and read its result with useLoaderData. No effects, no loading flags, no race conditions:
While the next route's loader runs, the current page stays on screen. Show a global pending indicator with useNavigation:
Mutations with actions and <Form>#
An action handles form submissions. React Router's <Form> sends the form data to the route's action, then re-runs the loaders so the page shows fresh data:
Error pages#
When a loader throws or a component crashes, React Router renders the nearest route's ErrorBoundary:
Put one on the root route so every page has a fallback.
Protecting routes#
Redirect logged-out users from a loader — it runs before the page renders, so private content never flashes:
Tip: client-side checks improve UX, but real security lives on your server. Always verify permissions in your API.
Deploying an SPA with routing#
Client-side routes like /courses/react don't exist as files on the server. If a user refreshes on that URL, the server must still send index.html. You'll configure this "SPA fallback" rewrite in the Deployment lesson.
Common mistakes#
- Importing from
react-router-dom(removed in v8) — usereact-routerandreact-router/dom. - Using
<a href>for internal links, which reloads the whole app. - Forgetting
<Outlet />in a layout, so child routes never appear. - Creating the router inside a component (it should be created once, at module level).
- Treating
useParams()values as numbers. - Forgetting the server rewrite, causing 404s on refresh in production.
What's next#
As apps grow, some components re-render more than they need to. Next: memoisation with useMemo, useCallback and memo.
Check your understanding
Quick quiz
1.In React Router v8, where do you import
RouterProviderfrom?2.What does
<Outlet />do in a parent route's component?3.Why use
<Link to="/about">instead of<a href="/about">for in-app navigation?
Finished reading?
Mark this lesson complete to track your progress.