react-router-dom is what turns a React app into a true single-page application, swapping components in and out as the URL changes instead of reloading the page. This lesson covers BrowserRouter, defining routes, safe client-side links, dynamic URL parameters, and navigating programmatically.
1The Single Page App
In a traditional website, clicking a link makes the browser request an entirely new HTML document from the server, causing a visible flash as the page reloads. A React Single Page Application (SPA) only ever loads one HTML file ā JavaScript then dynamically swaps which components are visible as the user navigates.
This is exactly what react-router-dom manages: keeping the visible components in sync with the URL, without ever triggering a full page reload, which is what makes navigation in an SPA feel instantaneous.
// Example
console.log("Running React SPA Router...");The SPA Paradigm
One HTML page, infinite dynamic views.
2BrowserRouter Provider
To enable routing at all, the entire component tree needs to be wrapped in a <BrowserRouter> component. Under the hood, BrowserRouter uses the HTML5 History API to keep React's internal notion of the current location in sync with the browser's actual URL bar.
This is also what makes the browser's back and forward buttons work correctly inside an SPA ā without this provider, none of React Router's other components or hooks have any routing context to work with.
import { BrowserRouter } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<AppContent />
</BrowserRouter>
);
}3Defining Routes
With the provider in place, actual paths get defined using a <Routes> container holding individual <Route> components. Each <Route> takes a path string describing what the URL should look like, and an element prop containing the JSX to render when that path matches.
A typical app lists several of these together, like <Route path="/" element={<Home />} /> and <Route path="/about" element={<About />} />, so <Routes> can render whichever one fits the current URL.
import { Routes, Route } from 'react-router-dom';
<Routes>
<Route path='/' element={<Home />} />
<Route path='/about' element={<About />} />
</Routes>Mapping URLs to Components
path ā”ļø element
4Client-Side Links
Standard HTML <a> tags must not be used for internal navigation, since they trigger a full page reload and destroy all React state. Instead, import <Link> from react-router-dom ā it renders as a real <a> tag in the DOM (good for SEO and accessibility), but overrides the click event to perform fast client-side routing instead.
A typical navbar looks like <Link to="/">Home</Link> and <Link to="/about">About</Link> ā visually identical to anchor tags, but without the destructive full-page reload.
import { Link } from 'react-router-dom';
<nav>
<Link to='/'>Home</Link>
<Link to='/about'>About</Link>
</nav>Never use <a href>
For internal app links.
5NavLink for Menus
Navigation menus often need to visually highlight whichever link corresponds to the current page. <NavLink> is a special version of <Link> that knows whether it's currently the active route.
Passing a function to its className (or style) prop, like ({ isActive }) => isActive ? 'active-link' : '', lets React Router apply conditional styling automatically based on whether that link's to matches the current URL.
<Routes>
<Route path='/' element={<Home />} />
<Route path='*' element={<NotFound />} />
</Routes>Active Styling
Use NavLink for navbars
6Catch-All 404 Pages
When a user visits a URL that doesn't match any defined route, the app needs a "Not Found" page. React Router makes this simple: add a <Route> at the very bottom of the <Routes> list with path="*", the wildcard.
Because <Routes> evaluates matches by specificity and * is the least specific possible pattern, this catch-all route only renders when no other, more specific route matched the current URL.
<Route path='/users/:id' element={<UserProfile />} />404
Page Not Found
7Dynamic URL Parameters
Routes that share a common shape, like a user profile page, don't need a separate route written out for every possible user. Prefixing a path segment with a colon, like /users/:id, tells React Router that segment is a dynamic parameter that should match any value.
A route can even have multiple dynamic segments, such as /store/:category/:item, letting a single <Route> definition handle an entire family of URLs.
function UserProfile() {
const { id } = useParams();
return <h2>User: {id}</h2>;
}Dynamic Paths
/resource/:variable
8The useParams Hook
Once a dynamic route matches, the actual value from the URL still needs to be read inside the rendered component ā that's what the useParams() hook is for. It returns an object containing every dynamic parameter defined in the matched route's path.
For a route defined as /users/:id, calling const { id } = useParams() inside the rendered component extracts whatever value was actually present in the URL, like '123' from /users/123.
const navigate = useNavigate();
const goHome = () => navigate('/');Extracting Data
useParams()
9Programmatic Navigation
<Link> is great for navigation the user triggers by clicking, but some navigation needs to happen from JavaScript logic itself ā redirecting after a login form successfully submits, for example. React Router's useNavigate hook returns a function for exactly this.
Calling navigate('/dashboard') inside an async success handler, after await api.login() resolves, redirects the user immediately without requiring any click at all.
<h1>Routing Master Unlocked!</h1>10Step-by-Step Breakdown
The Single Page App. Welcome to React Router DOM. In traditional websites, clicking a link causes the browser to make a full network request for a completely new HTML document, causing the screen to flash white. In a React Single Page Application (SPA), we only ever load ONE HTML file. We then use JavaScript to dynamically swap out which React components are currently visible, making navigation feel instantaneous.
BrowserRouter Provider. To enable routing in your application, you must wrap your entire component tree in a <BrowserRouter> component. This provider uses the HTML5 History API under the hood to sync your React state with the browser's URL bar, enabling the back/forward buttons to work seamlessly.
Defining Routes. Once the provider is in place, you define your actual paths using the <Routes> container and individual <Route> components. The <Route> component takes a path string (what the URL should look like) and an element prop containing the JSX you want to render when the URL matches that path.
Which prop on the <Route> component determines which JSX gets rendered when the URL matches?
- ārender
- ācomponent
- āelement
Client-Side Links. Crucially, you MUST stop using standard HTML <a> tags for internal links. An <a> tag will trigger a full page reload, destroying your React state. Instead, import the <Link> component from React Router. It renders as an <a> tag in the DOM for SEO and accessibility, but it overrides the click event to perform lightning-fast client-side routing.
Which component completely intercepts the browser's default click behavior to allow fast, client-side routing in an SPA?
- ā<a>
- ā<Link>
NavLink for Menus. For navigation menus, you often want to highlight the 'active' link. React Router provides <NavLink>, a special version of <Link> that knows whether or not it is the current active route. It automatically allows you to apply conditional classes or styles based on its active state.
Catch-All 404 Pages. What happens if a user types in a URL that doesn't exist? You need a 'Not Found' page. React Router makes this incredibly easy. Simply create a route at the very bottom of your <Routes> list and set the path to the wildcard asterisk (*). This route will only trigger if no previous routes matched the URL.
Dynamic URL Parameters. Often, you need routes that share a common pattern, like a user profile page. You don't want to write a route for every user. Instead, use Dynamic Segments. By prefixing a path segment with a colon (:), you tell React Router that this portion of the URL is a variable parameter.
If you want a route to match paths like /post/45 or /post/hello-world, how should you define the route path prop?
- ā"/post/$postId"
- ā"/post/:postId"
The useParams Hook. Once the dynamic route is matched, you need to read the actual value out of the URL (e.g., extracting '123' from /users/123). Inside the rendered component, you use the useParams() hook. It returns an object containing all the dynamic parameters defined in your route path.
Programmatic Navigation. Links are great for users clicking around, but sometimes you need to trigger navigation programmatically from your JavaScript logic. For example, redirecting the user AFTER they successfully submit a login form. For this, React Router provides the useNavigate hook.
Which hook should you use if you need to redirect a user to a success page immediately after an API fetch completes?
- āuseLocation
- āuseNavigate
Nested Routes. For complex layouts, React Router supports Nested Routes. Imagine a Dashboard where the sidebar stays the same, but the inner content changes between 'Profile' and 'Settings'. You can place <Route> tags INSIDE other <Route> tags to achieve this powerful layout architecture.
Mastery Achieved. Routing mastery achieved! You now know how to architect a modern React SPA using react-router-dom v6. You can define static and dynamic routes, catch 404 errors, use safe client-side links, and trigger programmatic redirects. You are ready to build multi-page apps!
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)
1Announce Route Changes to Assistive Technology
Because client-side navigation never triggers a full page reload, screen readers won't automatically announce a new page ā move focus to the new view's main heading after navigating so assistive technology users know the route changed.
2NavLink's isActive State Should Be Visually and Programmatically Clear
When highlighting the active link in a nav menu with `<NavLink>`, don't rely on color alone ā pair the active styling with `aria-current="page"` so assistive technology can also identify the current page.
SEO Implications
- 1
Every Route Needs a Real, Crawlable URL
Because `react-router-dom` handles navigation entirely client-side, make sure each `<Route path>` still corresponds to a distinct URL that can be requested directly and rendered with meaningful content, ideally via server-side rendering.
- 2
The Wildcard 404 Route Should Return the Right Status Semantics
A `path="*"` catch-all route improves UX for bad URLs, but if the app is server-rendered, make sure the actual HTTP response for unmatched routes reflects a 404 status, not a 200, so crawlers don't index broken URLs as valid pages.
Best Practices
Never Use a Plain <a> Tag for Internal Navigation
A native anchor tag forces a full page reload and destroys all in-memory React state; always use `<Link>` or `<NavLink>` for links that stay within the app.
Keep the Wildcard 404 Route Last in the List
Since `path="*"` matches literally any URL, place it as the final `<Route>` inside `<Routes>` so more specific routes are always evaluated and available to match first.
Frequent Bugs
useParams() returns undefined for a value that should be in the URL.
The parameter name used in `useParams()` doesn't match the name declared in the route's `path`. If the route is `/users/:id`, the destructured key must be `id`, e.g. `const { id } = useParams()`.
Clicking a navigation link causes the whole app to flash white and lose all state.
The link was written as a plain `<a href="...">` instead of React Router's `<Link to="...">`. Replace it with `<Link>` so the click is intercepted and handled via client-side routing instead of a full page request.
Real-World Examples
Dynamic User Profile Route
An app defines `<Route path="/users/:id" element={<UserProfile />} />` so a single component handles every user's profile page, reading the specific `id` out of the URL with `useParams()` to fetch that user's data.
function UserProfile() {
const { id } = useParams();
return <h2>Fetching data for user {id}...</h2>;
}