React apps that feel like multi-page websites, with distinct URLs for each view, rely on React Router to swap components in and out without ever reloading the page. This lesson covers wiring up BrowserRouter, defining dynamic routes, and navigating both declaratively with Link and programmatically with useNavigate.
1What is an SPA?
In a traditional multi-page website, every link click sends a request to the server for a brand-new HTML document, producing a visible flash and a full reload. A Single Page Application (SPA) avoids this entirely: the browser loads a single HTML file once, and from then on JavaScript swaps the visible content in and out to simulate moving between pages.
React Router is the library that makes this illusion work: it watches the browser's URL and decides, without ever triggering a server round-trip, which components should be mounted on screen at any given moment.
// React Router: Navigation without reloadsZero Reloads
Instant transitions
2React Router
React itself ships with no built-in routing solution ā the de facto standard library for this job is react-router-dom. It works by watching the browser's current URL and using that value to decide which of your components should be rendered at any given moment, all without a full page reload.
Installing it (npm install react-router-dom) gives you the building blocks ā BrowserRouter, Routes, Route, Link, and hooks like useParams and useNavigate ā needed to wire an entire multi-page-feeling app on top of a single HTML document.
<Route path='/about' element={<About />} />The Traffic Cop
Mapping URLs to Components
3The BrowserRouter
To make React Router work anywhere in your app, you wrap the entire component tree in a <BrowserRouter> at the root, typically right around <App />. This component plugs into the browser's History API, which is what lets it detect URL changes and keep the back and forward buttons behaving correctly.
Without this wrapper, none of React Router's other components ā Routes, Route, Link, or any of its navigation hooks ā have access to the routing context they need to function.
import { Link } from 'react-router-dom';
<Link to='/contact'>Contact Us</Link>The Foundation
Wrap your root component.
4Defining Routes
Route definitions live inside a <Routes> container, with each individual <Route> mapping one URL to one component. A Route takes two key props: path, the URL string to match, and element, the JSX that should render when that path is active.
React Router checks the current URL against each Route's path and renders only the matching element ā so <Route path="/about" element={<About />} /> shows the About component only when the browser is at /about.
<Route path='/user/:id' element={<Profile />} />Route Mapping
path ā element
5Navigation: The Link Component
Standard <a href> tags should never be used for in-app navigation in a React Router application, because clicking one tells the browser to fetch a whole new HTML document ā exactly the full-page reload an SPA is built to avoid. React Router's <Link> component replaces the anchor tag entirely.
A <Link to="/about"> intercepts the click, updates the URL via the History API, and lets React swap the rendered components instantly, with no reload and no white flash.
const { id } = useParams();
// id = '123' if URL is /user/123Ban the Anchor Tag
Link prevents the reload.
6Dynamic Route Parameters
Real apps need URLs that vary, like /users/1 or /users/42, without writing a separate Route for every possible ID. React Router supports this with dynamic segments: prefixing a piece of the path with a colon, as in path="/users/:userId", tells the router that segment is a variable rather than a literal string.
Any value in that position of the URL ā 1, bob, 99 ā will match the route, and the actual matched value becomes available to the rendered component.
const navigate = useNavigate();
const onLogin = () => {
navigate('/dashboard');
};Dynamic Segments
Using the colon :
7Extracting Parameters
Once a dynamic route like /users/:userId matches, the rendered component still needs to know the actual value that was in the URL ā for example, to fetch that specific user from an API. React Router's useParams hook provides exactly that.
Calling const { userId } = useParams() inside the matched component returns an object keyed by the dynamic segment names defined in the route, so visiting /users/42 gives you userId === '42' to use in your logic.
<Route path='settings' element={<Settings />}>
<Route path='profile' element={<Profile />} />
</Route>useParams
Extracting URL variables
8Programmatic Navigation
Not every navigation happens because a user clicked a <Link> ā sometimes you need to redirect from inside your own logic, like after a login form successfully submits. React Router's useNavigate hook is built for this.
Calling const navigate = useNavigate() gives you a function you can call anywhere in your component, such as navigate('/dashboard') after an async API call resolves, to programmatically change the URL and trigger React Router to render the new matching route.
/* Router Lab: Multi-Page SPA Rendered */Imperative Routing
Navigating from JS logic.
9Nested Routes
Routes can be nested inside other routes, which is the pattern behind layouts like a dashboard where a sidebar stays fixed but the central content changes with the URL. A parent <Route> wraps child <Route> elements, and the parent's component renders an <Outlet /> at the exact spot where the matching child should appear.
So visiting /dashboard/billing keeps the DashboardLayout (and its sidebar) mounted while swapping only the <Outlet /> content for the Billing component.
<Route path='*' element={<NotFound />} />The Outlet
Where children render
10Catch-all Routes (404)
A catch-all 404 route is created by placing a <Route> with path="*" at the very bottom of your <Routes> list. The asterisk works as a wildcard that matches any URL that wasn't already matched by a route defined above it.
Because React Router checks routes in order and stops at the first match, this wildcard route only ever renders when nothing else did, making it the natural place to render a NotFound component for unrecognized URLs.
<NavLink className={({ isActive }) => ...} />Handling 404s
path="*"
11NavLink (Active states)
<NavLink> is React Router's alternative to <Link> built specifically for navigation menus where you want to visually highlight whichever link corresponds to the current page. It behaves identically to <Link> for navigation purposes, but it also passes an isActive boolean into a function you provide for its className or style prop.
That means you can write className={({ isActive }) => isActive ? 'active-link' : ''} and have the active menu item style itself automatically as the URL changes.
// Data Loader PatternActive States
Automatic highlighting.
12Mastery Achieved
With BrowserRouter, Routes, dynamic parameters via useParams, programmatic redirects via useNavigate, nested layouts with Outlet, and a wildcard catch-all for 404s, you now have everything needed to build a complete, seamless single-page application. The URL changes, but the page never reloads.
The next step is connecting these routed views to real data, so the components rendered at each URL can fetch and display information from an API.
/* Next: API Integration (Fetching) */Navigation Mastered ā
13Step-by-Step Breakdown
What is an SPA?. Welcome to SPA Navigation. In traditional websites, clicking a link causes the browser to fetch a completely new HTML document from the server, resulting in a white flash and a full page reload. A Single Page Application (SPA) works differently: it loads exactly ONE HTML file, and JavaScript dynamically swaps out the content to simulate moving between pages.
React Router. React itself doesn't have a built-in router. The industry standard library for this is react-router-dom. It tracks the browser's URL and determines which React components should be visible on the screen at any given time.
The BrowserRouter. To use React Router, you must wrap your entire application in a <BrowserRouter>. This component connects your app to the browser's History API, allowing it to listen for URL changes and update the back/forward buttons correctly.
Defining Routes. Inside your app, you define a <Routes> container, and inside that, individual <Route> components. A Route takes two main props: path (the URL string) and element (the JSX to render when the URL matches).
Which prop on the <Route> component is used to specify the component that should be rendered?
- ācomponent
- āelement
Navigation: The Link Component. To move between pages, you must NEVER use standard <a href> tags. A standard link tells the browser to fetch a new document, breaking the SPA and reloading the whole app. Instead, you use React Router's <Link> component. It intercepts the click and updates the URL instantly.
What prop does the <Link> component use to define the destination URL?
- āhref
- āto
Dynamic Route Parameters. Often, you need URLs that are dynamic, like /users/1 or /users/42. You can define a dynamic parameter in your route path by prefixing a segment with a colon (:). This tells the router that this segment is a variable.
Extracting Parameters. Once you navigate to a dynamic route, the rendered component needs to know what that dynamic value is (e.g., to fetch the correct user from an API). You extract this value using the useParams hook provided by React Router.
Programmatic Navigation. Sometimes you need to change the page automatically from your logic, not because the user clicked a <Link>. For example, redirecting after a successful login or form submission. For this, we use the useNavigate hook.
Nested Routes. You can nest routes inside other routes! This is perfect for complex layouts like a Dashboard where a sidebar remains constant, but the central content changes based on the URL (e.g., /dashboard/settings vs /dashboard/billing). You render an <Outlet> component in the parent where the child routes should appear.
Which hook do you use to navigate from inside a component's logic (like a setTimeout callback)?
- āuseParams
- āuseNavigate
Catch-all Routes (404). To handle "404 Not Found" errors, you place a <Route> at the very bottom of your routing list with the path *. The asterisk acts as a wildcard. If none of the routes above it match the URL, this wildcard route will catch it.
NavLink (Active states). For navigation menus, you often want to highlight the link that corresponds to the page you are currently on. React Router provides <NavLink> which is just like <Link>, but it automatically passes an isActive boolean to its class/style props so you can style it dynamically.
Mastery Achieved. Router mastery achieved! You now know how to build a seamless, fast SPA with dynamic parameters, programmatic navigation, and nested layouts. Next up: integrating external data with API fetching!
Level Up š
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Manage Focus on Route Changes
Because React Router swaps components without a full page reload, the browser doesn't automatically reset focus or announce the new view to screen reader users the way a traditional page load does ā move focus to the new page's heading (or a dedicated announcement region) after each navigation.
2Use NavLink's aria-current for Active Navigation Items
NavLink can apply `aria-current="page"` to the link matching the current route, giving screen reader users an explicit signal of which navigation item represents the page they're on, beyond just a visual style difference.
SEO Implications
- 1
Client-Side Routes Need Server-Side Awareness for Direct Loads
If a user (or a search engine crawler) requests a deep URL like /users/42 directly, your server must be configured to serve the same index.html for all routes, or the request 404s before React Router ever gets a chance to render the matching component.
- 2
Each Route Should Set Its Own Title and Meta Tags
Because an SPA loads one HTML document, the page title and meta description won't change automatically as users navigate ā each routed component needs to update these itself (or via a framework's head-management APIs) so search engines and browser tabs reflect the current view.
Best Practices
Always Place the Wildcard Route Last
Since React Router matches routes in order and stops at the first match, a catch-all path="*" route placed before more specific routes would intercept every URL ā it must always be the final entry inside <Routes>.
Never Use <a href> for Internal Navigation
A plain anchor tag forces a full document reload, which reinitializes your entire React app and discards any in-memory state; use <Link> or <NavLink> for every internal link so the SPA's single-load model stays intact.
Frequent Bugs
Clicking an internal link causes a full page reload and a visible flash instead of an instant transition.
A standard <a href> tag was used instead of React Router's <Link to="...">, which is the only component that intercepts the click and updates the URL through the History API without requesting a new document.
useParams() returns undefined for a value that should be present in the URL.
The dynamic segment name used in useParams() doesn't match the name defined in the route's path ā the destructured key must exactly match the colon-prefixed segment, e.g. path="/users/:userId" requires const { userId } = useParams(), not const { id } = useParams().
Real-World Examples
Protected Dashboard With Nested Routes
A dashboard layout renders a persistent sidebar and header, with an <Outlet /> that swaps between Billing, Settings, and Profile sub-views as the user clicks between /dashboard/billing and /dashboard/settings, all without remounting the layout.
<Route path='/dashboard' element={<DashboardLayout />}>
<Route path='billing' element={<Billing />} />
<Route path='settings' element={<Settings />} />
</Route>