React Router — Complete Guide with Routing Examples
In this tutorial, you'll learn about React Router. We cover key concepts, practical examples, and best practices.
React Router is the standard routing library for React applications, enabling navigation between views, URL parameter handling, nested layouts, and lazy-loaded pages without full browser refreshes.
What You'll Learn
Set up React Router in your app — configure BrowserRouter, define routes with Route, navigate with Link and useNavigate, read URL params with useParams, build nested layouts, and handle 404 pages.
Why It Matters
Single-page apps need routing to feel like multi-page sites. Without a router, users can't bookmark pages, use the back button, or share URLs. React Router is the industry standard — used by 80%+ of React apps.
Real-World Use
An e-commerce site with product pages (/products/123), a blog with category and post routes (/blog/react-router), a dashboard with nested settings pages, or any app with multiple views.
Setting Up React Router
Install react-router-dom (the web version):
# Requires React 16.8+
npm install react-router-dom
This package provides browser-specific components like BrowserRouter, Link, and useNavigate. The core logic lives in react-router, but for web apps you always install react-router-dom.
BrowserRouter and Routes
BrowserRouter is the top-level component that enables routing. It uses the HTML5 history API to keep the UI in sync with the URL. Routes is the container that matches URL paths to components.
import { BrowserRouter, Routes, Route } 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>
<nav style={{ padding: "10px", borderBottom: "1px solid #ccc" }}>
<a href="/">Home</a> | <a href="/about">About</a> |{" "}
<a href="/contact">Contact</a>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</BrowserRouter>
);
}
How it works:
BrowserRouterwraps the entire app and listens for URL changes.Routeslooks through its childRoutecomponents and renders the first matching one.- Each
Routehas apath(what URL to match) and anelement(what component to render).
Expected output: Navigating to /about renders "About Us", navigating to / renders "Home Page".
Link Component (Don't Use Tags)
Regular <a href="/about"> causes a full page reload — defeating the purpose of a single-page app. Use Link instead, which navigates client-side without reloading.
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
function App() {
return (
<BrowserRouter>
<nav style={{ padding: "10px", display: "flex", gap: "15px" }}>
<Link to="/" style={{ textDecoration: "none", color: "#0066cc" }}>
Home
</Link>
<Link to="/about" style={{ textDecoration: "none", color: "#0066cc" }}>
About
</Link>
<Link to="/contact" style={{ textDecoration: "none", color: "#0066cc" }}>
Contact
</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</BrowserRouter>
);
}
NavLink is a variant that adds an active class to the current link:
import { NavLink } from "react-router-dom";
<NavLink
to="/about"
style={({ isActive }) => ({
fontWeight: isActive ? "bold" : "normal",
color: isActive ? "#ff0000" : "#0066cc",
})}
>
About
</NavLink>
URL Parameters with useParams
Dynamic routes like /users/42 or /products/shoes use URL parameters. The useParams hook returns an object of key-value pairs from the URL.
import { useParams } from "react-router-dom";
function UserProfile() {
// If URL is /users/42, params = { id: "42" }
const { id } = useParams();
return (
<div style={{ padding: "20px" }}>
<h2>User Profile</h2>
<p>Viewing user {id}</p>
</div>
);
}
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/users/:id" element={<UserProfile />} />
</Routes>
</BrowserRouter>
);
}
How it works: The colon (:id) in the path creates a parameter. When the URL is /users/42, React Router sets id = "42". useParams() returns { id: "42" }.
Multiple parameters:
// Path: /products/:category/:productId
// URL: /products/electronics/123
// useParams() → { category: "electronics", productId: "123" }
Programmatic Navigation with useNavigate
Sometimes you need to navigate after an action — like redirecting after a form submit. Use useNavigate for this.
import { useNavigate } from "react-router-dom";
function LoginPage() {
const navigate = useNavigate();
function handleLogin() {
// Simulate login
console.log("Logging in...");
// Redirect to dashboard after login
navigate("/dashboard");
}
function handleCancel() {
// Go back to previous page
navigate(-1);
}
return (
<div style={{ padding: "20px" }}>
<h2>Login</h2>
<button onClick={handleLogin}>Login</button>
<button onClick={handleCancel}>Cancel</button>
</div>
);
}
Alternatives:
navigate("/path")— navigate to a pathnavigate("/path", { replace: true })— replace current history entry (no back button)navigate(-1)— go back one pagenavigate(1)— go forward one page
Nested Routes with Outlet
Nested routes let you share a common layout (like a sidebar or header) across multiple pages. The parent component renders an <Outlet /> where child routes appear.
import { BrowserRouter, Routes, Route, Link, Outlet } from "react-router-dom";
function DashboardLayout() {
return (
<div style={{ display: "flex" }}>
<nav
style={{
width: "200px",
padding: "20px",
borderRight: "1px solid #ccc",
minHeight: "100vh",
}}
>
<h3>Dashboard</h3>
<ul style={{ listStyle: "none", padding: 0 }}>
<li><Link to="/dashboard">Overview</Link></li>
<li><Link to="/dashboard/settings">Settings</Link></li>
<li><Link to="/dashboard/profile">Profile</Link></li>
</ul>
</nav>
<main style={{ padding: "20px", flex: 1 }}>
<Outlet />
</main>
</div>
);
}
function Overview() {
return <h2>Dashboard Overview</h2>;
}
function Settings() {
return <h2>Account Settings</h2>;
}
function Profile() {
return <h2>User Profile</h2>;
}
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/dashboard" element={<DashboardLayout />}>
<Route index element={<Overview />} />
<Route path="settings" element={<Settings />} />
<Route path="profile" element={<Profile />} />
</Route>
</Routes>
</BrowserRouter>
);
}
Key concepts:
- The parent route (
/dashboard) has noindexelement of its own — it renders the layout. <Route index element={<Overview />} />renders when the URL exactly matches/dashboard.- Child routes like
/dashboard/settingsrender inside the parent's<Outlet />. - The path on child routes is relative to the parent (just
"settings", not/dashboard/settings).
404 Pages (No Match)
Any route that doesn't match an existing path should show a "Not Found" page. Use a Route with path="*" as the last route inside Routes.
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
{/* Catch-all — must be last */}
<Route path="*" element={<NotFound />} />
</Routes>
function NotFound() {
return (
<div style={{ textAlign: "center", padding: "50px" }}>
<h1>404</h1>
<p>Page not found.</p>
<Link to="/">Go Home</Link>
</div>
);
}
Lazy Loading with React.lazy
For large apps, loading all components upfront wastes bandwidth. Split your routes into separate chunks with React.lazy and Suspense.
import { lazy, Suspense } from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
// These components are loaded on demand
const Home = lazy(() => import("./pages/Home"));
const About = lazy(() => import("./pages/About"));
const Contact = lazy(() => import("./pages/Contact"));
function App() {
return (
<BrowserRouter>
<Suspense
fallback={
<div style={{ textAlign: "center", padding: "50px" }}>
Loading...
</div>
}
>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
Expected behavior: The first time a user visits /about, React fetches the About component code from a separate JavaScript chunk. The Suspense fallback shows while loading. Subsequent visits use the cached chunk instantly.
This pattern is critical for performance in production apps and is used by platforms like Doda Browser to reduce initial bundle sizes.
useSearchParams (Query Strings)
Read and update URL query parameters like ?q=react&page=2:
import { useSearchParams } from "react-router-dom";
function SearchPage() {
const [searchParams, setSearchParams] = useSearchParams();
const query = searchParams.get("q") || "";
const page = Number(searchParams.get("page")) || 1;
function updateQuery(newQuery) {
setSearchParams({ q: newQuery, page: 1 });
}
return (
<div style={{ padding: "20px" }}>
<input
value={query}
onChange={e => updateQuery(e.target.value)}
placeholder="Search..."
style={{ padding: "8px", fontSize: "16px" }}
/>
<p>Searching for: "{query}" — Page {page}</p>
</div>
);
}
Common Errors with React Router
1. Forgetting to wrap with BrowserRouter
Calling useRoutes, useParams, or useNavigate outside a <BrowserRouter> throws: "useRoutes() may be used only in the context of a
Fix: Wrap the entire app (or at minimum the routing sections) with BrowserRouter.
2. Using instead of
Regular anchor tags cause full page reloads, losing application state.
Fix: Use <Link to="/path"> for client-side navigation.
3. Route order matters
React Router v6 renders the first match in <Routes>. A broad path like / should come after specific ones.
Fix: Put more specific routes first, catch-all path="*" last.
4. Misunderstanding index routes
An index route is the default child rendered at the parent path. Forgetting <Route index> means nothing renders at the parent URL.
5. Using exact prop (v5 vs v6)
In React Router v6, routes are exact by default. The exact prop from v5 is removed.
6. Not wrapping lazy routes in Suspense
React.lazy components must be rendered inside a <Suspense> boundary with a fallback.
7. Paths with leading slashes in nested routes
Child route paths should NOT start with /. A path "/settings" would be treated as absolute, not nested under the parent.
8. Typo: useNavigate vs useNavigation
The correct hook is useNavigate (no -). useNavigation is not a valid React Router export.
Practice Questions
What is the difference between
LinkandNavLink?NavLinkadds styling props based on whether the link matches the current URL (likeisActive).Linkdoes not.How do you read a URL parameter like
/users/42? Define the path as/users/:id, then calluseParams()which returns{ id: "42" }.What does
<Outlet />do? It's a placeholder in a layout component where matched child routes render. It enables nested routing with shared layouts.How do you redirect after a form submission? Call
navigate("/target-path")from theuseNavigatehook inside the submit handler.How do you lazy-load a route component? Use
const Component = lazy(() => import("./Component"))and wrap it in<Suspense fallback={...}>.
Challenge
Build a product catalog with:
/products— list all products/products/:id— product detail page/products/:id/reviews— product reviews (nested)/cart— shopping cart- A 404 page for invalid routes
- Lazy-loaded product pages
- A "Back to Products" link on the detail page using
useNavigate
Real-World Task: Blog with Categories
Create a routing structure for a blog:
// Routes structure:
// /blog — blog home (list of recent posts)
// /blog/:category — filter by category (e.g., /blog/react)
// /blog/:category/:slug — single post (e.g., /blog/react/react-router-guide)
// All routes share a BlogLayout with sidebar
function BlogLayout() {
return (
<div style={{ display: "grid", gridTemplateColumns: "3fr 1fr", gap: "20px" }}>
<main><Outlet /></main>
<aside style={{ background: "#f5f5f5", padding: "15px" }}>
<h3>Categories</h3>
<Link to="/blog">All</Link><br />
<Link to="/blog/react">React</Link><br />
<Link to="/blog/javascript">JavaScript</Link><br />
<Link to="/blog/css">CSS</Link>
</aside>
</div>
);
}
This pattern is similar to how security documentation sites structure their content with categorized, nested routing.
Learning Path
graph LR
A[React Basics] --> B[useState & useEffect]
B --> C[Context API & useReducer]
C --> D[Custom Hooks]
D --> E[useRef Hook]
E --> F[React Router]
F --> G[React Forms]
F --> H[Performance Optimization]
style F fill:#ff6b6b,stroke:#333,stroke-width:2px,color:#fff
Related Tutorials
- React Tutorial for Beginners — fundamentals before routing
- React Hooks Complete Guide — hooks used with routers
- useState Hook Guide — managing state in routed pages
- useEffect Hook Guide — data fetching on route enter
- Learn about React Performance Optimization with lazy loading
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. Last updated: June 2026.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro