React Router: Introduction
A traditional multi-page website loads a fresh HTML document from the server every time the user clicks a link. A Single Page Application (SPA) loads once and then swaps content in and out using JavaScript — no full-page reload, no white flash, no lost scroll position. This is client-side routing, and React Router is the standard library for implementing it in React.
What Client-Side Routing Does
Intercepts link clicks — instead of letting the browser navigate, React Router prevents the default behaviour and updates the URL via the History API.
Matches the URL to a component tree and renders the matching components.
Preserves application state — the React tree stays alive; only the matched components change.
Enables deep linking — each view has its own URL that users can bookmark and share.
Installation
npm install react-router-dom
Minimal Working Router
Here is the smallest complete React Router v6 application. Study the structure — three concepts are doing all the work: BrowserRouter, Routes, and Route:
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom'
function Home() { return <h1>Home Page</h1> }
function About() { return <h1>About Us</h1> }
function Contact(){ return <h1>Contact</h1> }
function App() {
return (
<BrowserRouter>
{/* Navigation — renders <a> tags that don't cause page reloads */}
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Link to="/contact">Contact</Link>
</nav>
{/* Route declarations — only one renders at a time */}
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</BrowserRouter>
)
}The Core Building Blocks
<BrowserRouter> — The top-level provider that reads the current
URL from the browser's History API and makes it available to all nested
components. Wrap your entire app with it once, usually in main.jsx.
<Routes> — The container that looks at the current URL and renders
the first matching <Route>. Think of it as a switch statement for
URLs. In v6 this replaced <Switch> from v5.
<Route path="..." element={...} /> — Declares that when the URL
matches path, React Router should render the element. The element
prop accepts JSX (not a component reference), so you can pass props
inline: element={<UserPage userId={1} />}.
<Link to="..."> — Renders an anchor tag that updates the URL
without a page reload when clicked. Always use <Link> instead of
<a href="..."> for internal navigation.
NavLink: Active Link Styling
<NavLink> is a special version of <Link> that knows whether it is
currently active. It automatically adds an active CSS class to the
rendered anchor when its path matches the current URL:
import { NavLink } from 'react-router-dom'
function Nav() {
return (
<nav>
{/* 'active' class added automatically when URL matches */}
<NavLink to="/" end>Home</NavLink>
<NavLink to="/about">About</NavLink>
<NavLink to="/blog">Blog</NavLink>
</nav>
)
}
// Custom active style via callback
<NavLink
to="/about"
className={({ isActive }) => isActive ? 'link link--active' : 'link'}
style={({ isActive }) => ({ fontWeight: isActive ? 'bold' : 'normal' })}
>
About
</NavLink>React Router v6 vs v5
Concept | v5 | v6 |
|---|---|---|
Route container | <Switch> | <Routes> |
Route element | component={Home} | element={<Home />} |
Nested routes | Manually nested inside components | Declarative in route config |
Redirects | <Redirect to="/" /> | <Navigate to="/" /> |
useHistory | useHistory() | useNavigate() |
Exact matching | exact prop required | Exact by default |
Route ranking | First match wins | Best match wins |
A404 / Catch-All Route
function NotFound() {
return <h1>404 — Page Not Found</h1>
}
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="*" element={<NotFound />} /> {/* matches anything else */}
</Routes>React Router vs Framework Routing
React Router (SPA) | Next.js (framework) | Remix (framework) | |
|---|---|---|---|
Routing type | Client-side | File-system + hybrid | File-system + nested |
SSR support | No (SPA only) | Yes (built-in) | Yes (first-class) |
Data loading | Manual + TanStack Query | fetch in RSC / getServerSideProps | loader functions |
Bundle | react-router-dom pkg | Built-in | Built-in |
Use when | Vite SPA, CRA, custom setup | Marketing sites, full-stack apps | Data-heavy web apps |