A traditional website navigates between pages by making a full HTTP request to the server for each URL โ the server responds with a new HTML document, the browser tears down the current page, and renders the new one. A Single-Page Application (SPA) like the MERN Blog works differently: the browser loads one HTML file once, and all subsequent navigation is handled entirely in JavaScript โ no full page reloads, no round-trips to the server for HTML. React Router is the library that makes this possible. It intercepts browser navigation events, matches the current URL against your route definitions, and renders the corresponding React component โ giving users the feel of a multi-page site while keeping the speed and interactivity of an SPA.
Traditional vs Client-Side Navigation
| Traditional Multi-Page | SPA with React Router | |
|---|---|---|
| User clicks a link | Browser sends GET request to server | JavaScript intercepts โ no network request |
| What loads | Server returns full HTML document | React Router swaps out the rendered component |
| Page flash | White flash between pages | Instant โ no flash |
| Data fetching | Server pre-renders data into HTML | React component fetches data via API after render |
| Back/Forward button | Browser handles via HTTP cache | React Router manages via browser History API |
| URL in address bar | Reflects the actual server path | React Router updates it via history.pushState() |
/posts/getting-started-with-mern and share it. When they open that bookmark, the browser sends the request to your server. Your Express server (or hosting platform) must be configured to serve the React app’s index.html for all routes โ then React Router takes over and renders the correct component. This is why Express serves app.get('*', sendIndex) in production builds.Switch was replaced by Routes, and Redirect was replaced by Navigate. Most tutorials and Stack Overflow answers you will find for React Router are for v5, which has a different API. Always check that the documentation or tutorial you are reading matches the version in your package.json.index.html for all URL paths. On Netlify this is a _redirects file with /* /index.html 200. On Render this is a “Rewrite all paths to index.html” rule. Without this, navigating directly to /posts/123 returns a 404 from the server.How React Router Works Under the Hood
Browser address bar: https://mernblog.com/posts/getting-started-with-mern
1. User clicks a <Link to="/posts/getting-started-with-mern"> component
2. React Router calls window.history.pushState() โ updates URL without reload
3. React Router reads the new URL and matches it against your route definitions
4. React Router renders the component for the matched route (PostDetailPage)
5. PostDetailPage uses useParams() to get the slug from the URL
6. PostDetailPage fetches the post from the Express API using the slug
7. Post data arrives and React renders the full post โ all without a page reload
Back button:
1. Browser fires the popstate event
2. React Router listens for popstate and reads the new (previous) URL
3. React Router re-renders the component for the previous URL
React Router v6 โ Key Components and Hooks
| Name | Type | Purpose |
|---|---|---|
BrowserRouter |
Component | Provides routing context to the entire app โ wraps App in main.jsx |
Routes |
Component | Container for route definitions โ renders the first matching Route |
Route |
Component | Maps a URL path to a component |
Link |
Component | Client-side navigation link โ replaces <a href> |
NavLink |
Component | Like Link but adds active class when the URL matches |
Navigate |
Component | Programmatically redirects when rendered |
Outlet |
Component | Renders the matched child route inside a layout route |
useNavigate |
Hook | Navigate programmatically from event handlers |
useParams |
Hook | Read URL parameters (:id, :slug) |
useLocation |
Hook | Read current URL path, search, and state |
useSearchParams |
Hook | Read and update URL query string parameters |
The MERN Blog Route Map
Public routes:
/ โ HomePage (list of published posts)
/posts/:id โ PostDetailPage (single post)
/login โ LoginPage (login form)
/register โ RegisterPage (register form)
Protected routes (auth required):
/dashboard โ DashboardPage (user's posts)
/posts/new โ CreatePostPage (new post form)
/posts/:id/edit โ EditPostPage (edit post form)
Utility routes:
* โ NotFoundPage (404 โ no route matched)
Common Mistakes
Mistake 1 โ Using <a href> instead of <Link>
โ Wrong โ native anchor causes a full page reload:
<a href="/posts/123">Read More</a>
// Browser sends a GET request to the server โ page reloads, all state is lost
โ Correct โ use Link for in-app navigation:
import { Link } from 'react-router-dom';
<Link to="/posts/123">Read More</Link> // โ client-side โ no reload
Mistake 2 โ Forgetting to configure the server for SPA fallback
โ Wrong โ user bookmarks /posts/123 and gets a 404 from the hosting server.
โ
Correct โ configure the server or hosting platform to serve index.html for all paths. On Netlify: create public/_redirects with content /* /index.html 200. On Render: set the rewrite rule to serve index.html for all paths.
Mistake 3 โ Mixing React Router v5 and v6 APIs
โ Wrong โ using v5 components in a v6 project:
import { Switch, Redirect } from 'react-router-dom'; // v5 โ does not exist in v6
<Switch><Route exact path="/" component={Home} /></Switch>
โ Correct โ v6 API:
import { Routes, Route, Navigate } from 'react-router-dom'; // v6 โ
<Routes><Route path="/" element={<Home />} /></Routes>
Quick Reference
| Concept | React Router v6 |
|---|---|
| Wrap app in router | <BrowserRouter><App /></BrowserRouter> |
| Define routes | <Routes><Route path="/" element={<Home />} /></Routes> |
| Navigate link | <Link to="/about">About</Link> |
| Programmatic nav | const nav = useNavigate(); nav('/dashboard') |
| URL param | const { id } = useParams() |
| Redirect (render) | <Navigate to="/login" replace /> |
| Catch-all 404 | <Route path="*" element={<NotFound />} /> |