A traditional multi-page website sends a new HTML document from the server on every click. A Single Page Application (SPA) loads one HTML document and then updates the page content using JavaScript — the server is only asked for data (JSON from the API), never for new pages. React Router gives React SPAs the appearance of multiple pages by mapping URL paths to different components, using the browser’s History API to update the URL without triggering a full page load. The result: fast, app-like navigation with shareable, bookmarkable URLs.
How Client-Side Routing Works
Traditional server-side routing:
User clicks /posts/42
→ Browser sends GET /posts/42 to server
→ Server returns a new HTML page
→ Full page reload, all JS/CSS re-downloaded
Client-side routing (React Router):
User clicks /posts/42
→ React Router intercepts the click (e.preventDefault())
→ URL bar updates to /posts/42 via History API (pushState)
→ React Router renders the <PostDetailPage> component
→ No server request, no page reload — instant navigation
→ Components fetch data from /api/posts/42 as needed
On direct URL access (/posts/42):
→ Server returns index.html (the SPA shell)
→ React loads, React Router reads the URL
→ Renders <PostDetailPage> directly
(Requires server configuration: serve index.html for all routes)
react-router-dom v6.x). The key changes: Switch is replaced by Routes, route matching is exact by default, nested routes use Outlet, and useHistory is replaced by useNavigate. If you find older tutorials using Switch or component={PostCard} props, those are v5 patterns that will not work in v6.index.html for all unmatched routes by default. For production deployment, configure your web server (Nginx, Apache) to serve index.html for all routes: try_files $uri $uri/ /index.html; in Nginx. Without this, a user who bookmarks /posts/42 and visits directly will get a 404 from the server because the server does not have a /posts/42 file.<a href="..."> tags for internal navigation in a React Router application. An <a> tag causes a full page reload, destroying all React state and re-downloading the application. Always use React Router’s <Link to="..."> or <NavLink to="..."> for internal links. External links (other websites) still use regular <a> tags with target="_blank" rel="noopener noreferrer".Installation
npm install react-router-dom
// src/main.jsx — wrap the app with BrowserRouter
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./App.jsx";
import "./index.css";
createRoot(document.getElementById("root")).render(
<StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</StrictMode>
);
// BrowserRouter provides the routing context that all Router hooks need
// It must wrap any component that uses useNavigate, useParams, Link, etc.
Router Types
| Router | URL Format | Server Needed | Use For |
|---|---|---|---|
BrowserRouter |
/posts/42 |
Yes (serve index.html) | Production SPAs |
HashRouter |
/#/posts/42 |
No (hash is client-only) | Static hosting without server config |
MemoryRouter |
Not visible | No | Tests, React Native |
Common Mistakes
Mistake 1 — Using <a> instead of <Link> for internal navigation
❌ Wrong — full page reload, loses all React state:
<a href="/posts/42">Read Post</a> // causes full page reload!
✅ Correct — React Router’s Link for internal navigation:
import { Link } from "react-router-dom";
<Link to="/posts/42">Read Post</Link> // ✓ no page reload
Mistake 2 — Not wrapping the app with BrowserRouter
❌ Wrong — hooks throw: “useNavigate may be used only in the context of a Router”:
createRoot(document.getElementById("root")).render(<App />); // no BrowserRouter!
✅ Correct — wrap with BrowserRouter in main.jsx.
Quick Reference
| Concept | Details |
|---|---|
| Install | npm install react-router-dom |
| Wrap app | <BrowserRouter><App /></BrowserRouter> in main.jsx |
| Internal link | <Link to="/path"> (not <a href>) |
| Active link | <NavLink to="/path" className={({isActive}) => ...}> |
| URL params | const { id } = useParams() |
| Navigate | const navigate = useNavigate(); navigate("/path") |