What is Client-Side Routing and How React Router Works

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()
Note: Even though React Router handles navigation client-side, the URLs must still be real โ€” users can bookmark /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.
Tip: React Router v6 (the current major version) introduced a significantly cleaner API than v5 โ€” nested routes are first-class, 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.
Warning: React Router does not handle server-side rendering (SSR) by itself โ€” it is a client-side library. If you deploy a Vite React app to a static host or CDN, you must configure the host to return 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

โŒ 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 />} />

🧠 Test Yourself

A user bookmarks https://mernblog.com/posts/123 and opens it directly in the browser the next day. The server is configured to serve only the root / path. What happens?