Quick answer
Create routes in React using React Router v6 in 4 steps:
- Install React Router:
npm install react-router-dom - Wrap your app with
<BrowserRouter>in main.jsx - Define routes with
<Routes>and<Route element={} path="" /> - Add navigation with
<Link>and<Outlet>for nested pages
That’s it. You now have a multi-page React app with seamless navigation.
What is routing in React?
Routing is the ability to navigate between different pages or views in your React application without reloading the entire page. Instead of refreshing the browser, JavaScript changes what’s displayed based on the URL.
Example: When a user clicks “About”, the URL changes from localhost:5173/ to localhost:5173/about, and React renders the About component instead of the Home component.
This creates the illusion of multiple pages, but it’s all one single-page application (SPA). No server round-trips, no full page refresh. This is why modern React apps feel fast and responsive.
Why routing matters
Without routing, your React app is limited to a single page. With routing, you can build:
- Multi-page websites (Home, About, Contact, Blog)
- E-commerce stores (Products, Product detail, Cart, Checkout)
- Dashboards (Overview, Analytics, Settings, Users)
- Admin panels with complex navigation hierarchies
React Router is the de facto standard for routing in React. It’s maintained by Remix (the team behind modern full-stack React), and it’s what the React + VS Code setup guide points to when you’re ready for navigation.
Prerequisites
- A React app running with Vite (follow the Vite setup guide if you haven’t already)
- Understanding of npm and packages (we’ll install react-router-dom)
- VS Code or any text editor
- ~15 minutes
Step 1: Install React Router v6
In your project directory, run:
bash
npm install react-router-dom
This downloads React Router v6 (the latest, modern version released in 2022) and adds it to your package.json.
Output:
added 3 packages in 2s
That’s it. React Router is now available in your project.
Step 2: Set up BrowserRouter in your app
Open your src/main.jsx file (or index.jsx depending on your Vite template).
BEFORE:
jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
AFTER: Wrap your App with BrowserRouter
jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import App from './App.jsx'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>,
)
What BrowserRouter does:
- Monitors URL changes
- Tells React Router which component to render based on the current URL
- Enables the
<Link>component to work without full page reloads
Think of it as the engine that powers all routing in your app. Everything else (routes, links, navigation) depends on this wrapper.
Step 3: Create your first routes
Now, in your src/App.jsx, define which pages exist and what components should render for each.
Basic example:
jsx
import { Routes, Route } from 'react-router-dom'
import Home from './pages/Home'
import About from './pages/About'
import Contact from './pages/Contact'
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
)
}
export default App
What’s happening:
<Routes>— Container for all your routes<Route path="" element={} />— Defines one route (React Router v6 syntax)path="/"— URL pathelement={<Home />}— Component to render (v6 useselement, notcomponent)
When a user visits /about, React Router renders the About component at that URL.
Step 4: Add navigation with Links
Users need a way to navigate between pages. Use the <Link> component instead of <a> tags.
Create a src/components/Navigation.jsx:
jsx
import { Link } from 'react-router-dom'
function Navigation() {
return (
<nav style={{ padding: '1rem', backgroundColor: '#f0f0f0' }}>
<ul style={{ display: 'flex', listStyle: 'none', gap: '2rem' }}>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/contact">Contact</Link></li>
</ul>
</nav>
)
}
export default Navigation
Now import and use Navigation in your App:
jsx
import { Routes, Route } from 'react-router-dom'
import Navigation from './components/Navigation'
import Home from './pages/Home'
import About from './pages/About'
import Contact from './pages/Contact'
function App() {
return (
<>
<Navigation />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</>
)
}
export default App
Why <Link> instead of <a>:
<a href="/about">→ Full page reload (slow)<Link to="/about">→ Only re-renders the component (fast, no refresh)
Advanced routing patterns
1. Dynamic routes with parameters
Sometimes you need routes that change based on data. For example, a blog where each post has a unique ID.
Route definition:
jsx
<Route path="/blog/:id" element={<BlogPost />} />
Accessing the parameter in your component:
jsx
import { useParams } from 'react-router-dom'
function BlogPost() {
const { id } = useParams()
return (
<div>
<h1>Blog Post #{id}</h1>
{/* Fetch post data using the id */}
</div>
)
}
Now when a user visits /blog/42, the id parameter is 42.
Real example:
jsx
// Route: /users/:userId/posts/:postId
const { userId, postId } = useParams()
// User visits: /users/15/posts/87
// userId = "15", postId = "87"
2. Nested routes
Complex apps often have routes within routes. For example:
/dashboard
├─ /dashboard/overview
├─ /dashboard/analytics
└─ /dashboard/settings
Define nested routes:
jsx
<Route path="/dashboard" element={<DashboardLayout />}>
<Route path="overview" element={<Overview />} />
<Route path="analytics" element={<Analytics />} />
<Route path="settings" element={<Settings />} />
</Route>
In DashboardLayout.jsx, use <Outlet> to render child routes:
jsx
import { Link, Outlet } from 'react-router-dom'
function DashboardLayout() {
return (
<div style={{ display: 'flex' }}>
<nav style={{ width: '200px', borderRight: '1px solid #ccc' }}>
<ul style={{ listStyle: 'none', padding: '1rem' }}>
<li><Link to="/dashboard/overview">Overview</Link></li>
<li><Link to="/dashboard/analytics">Analytics</Link></li>
<li><Link to="/dashboard/settings">Settings</Link></li>
</ul>
</nav>
<main style={{ flex: 1, padding: '1rem' }}>
<Outlet /> {/* Child route renders here */}
</main>
</div>
)
}
<Outlet> is a placeholder where child routes render. Without it, child routes won’t display.
3. Handling 404 pages
What if a user visits a URL that doesn’t exist? Create a catch-all route:
jsx
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route path="*" element={<NotFound />} /> {/* Catch all */}
</Routes>
The path="*" route matches any URL that didn’t match above. Put it last.
NotFound.jsx:
jsx
import { Link } from 'react-router-dom'
function NotFound() {
return (
<div style={{ textAlign: 'center', padding: '2rem' }}>
<h1>404 - Page Not Found</h1>
<p>The page you're looking for doesn't exist.</p>
<Link to="/">Go back home</Link>
</div>
)
}
4. Programmatic navigation with useNavigate
Sometimes you need to navigate after an action (form submission, login, etc.). Use the useNavigate hook:
jsx
import { useNavigate } from 'react-router-dom'
function LoginForm() {
const navigate = useNavigate()
const handleSubmit = (e) => {
e.preventDefault()
// Validate login...
// If successful:
navigate('/dashboard')
}
return (
<form onSubmit={handleSubmit}>
<input type="email" placeholder="Email" />
<input type="password" placeholder="Password" />
<button type="submit">Login</button>
</form>
)
}
After login, the user is redirected to /dashboard programmatically.
Real-world example: E-commerce app
Here’s a complete routing setup for a small e-commerce app:
jsx
import { Routes, Route } from 'react-router-dom'
import Navigation from './components/Navigation'
import Home from './pages/Home'
import Products from './pages/Products'
import ProductDetail from './pages/ProductDetail'
import Cart from './pages/Cart'
import Checkout from './pages/Checkout'
import NotFound from './pages/NotFound'
function App() {
return (
<>
<Navigation />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/products" element={<Products />} />
<Route path="/products/:id" element={<ProductDetail />} />
<Route path="/cart" element={<Cart />} />
<Route path="/checkout" element={<Checkout />} />
<Route path="*" element={<NotFound />} />
</Routes>
</>
)
}
export default App
Flow:
- User lands on
/(Home) - Clicks “Products” →
/products(all products) - Clicks a product →
/products/42(ProductDetail gets id=42) - Adds to cart →
/cart - Clicks checkout →
/checkout - Visits invalid URL →
/products/not-found→ 404 page
React Router v6 vs. v5: Key differences
If you’re migrating from React Router v5, here’s what changed:
| Feature | v5 | v6 |
|---|---|---|
| Component prop | <Route component={Home} /> | <Route element={<Home />} /> |
| Render function | <Route render={() => ...} /> | <Route element={<Home />} /> |
| Exact matching | <Route exact path="/" /> | Path matching is exact by default |
| Switch component | <Switch> | <Routes> |
| Navigate component | <Redirect /> | <Navigate /> |
| Hooks | Limited | useParams, useNavigate, useLocation, useSearchParams |
| Nested routes | Flat structure | Native nested <Route> support |
| Relative paths | Not supported | Supported (cleaner, less repetition) |
v6 example (cleaner):
<Route path="/dashboard" element={<DashboardLayout />}>
<Route path="overview" element={<Overview />} /> {/* Relative */}
<Route path="analytics" element={<Analytics />} /> {/* Relative */}
</Route>
v5 equivalent (verbose):
<Route path="/dashboard" component={DashboardLayout} />
<Route path="/dashboard/overview" component={Overview} />
<Route path="/dashboard/analytics" component={Analytics} />
Common routing patterns and mistakes
✅ Correct: Use relative paths in nested routes
<Route path="/dashboard" element={<Dashboard />}>
<Route path="analytics" element={<Analytics />} /> {/* Correct */}
</Route>
❌ Wrong: Repeating full paths
<Route path="/dashboard" element={<Dashboard />}>
<Route path="/dashboard/analytics" element={<Analytics />} /> {/* Redundant */}
</Route>
✅ Correct: Use useNavigate for conditional navigation
const navigate = useNavigate()
if (isLoggedIn) {
navigate('/dashboard')
} else {
navigate('/login')
}
❌ Wrong: Using window.location for SPA navigation
window.location.href = '/dashboard' // Full page reload (breaks SPA)
✅ Correct: Link components don’t reload
<Link to="/about">About</Link> {/* Client-side, instant */}
❌ Wrong: Using <a> tags for internal navigation
<a href="/about">About</a> {/* Full page reload */}
Comparison: React Router vs. alternatives
| Library | Learning curve | Popularity | Performance | Best for |
|---|---|---|---|---|
| React Router v6 | Moderate | Very high (90%+ of projects) | Excellent | Most React apps |
| TanStack Router | Moderate-high | Growing | Excellent (Vite-native) | Modern, type-safe projects |
| Next.js (file-based) | Easy | Very high | Excellent | Full-stack React apps |
| Remix | Moderate | Growing | Excellent | Full-stack, server-heavy |
| Wouter | Easy | Low (alternative) | Good | Small, lightweight apps |
For this guide: Use React Router v6. It’s the standard, has the best documentation, and integrates seamlessly with your Vite setup.
FAQ
Q: What’s the difference between <Link> and <a>?
A: <Link> prevents full page reloads, keeping your SPA fast. <a> reloads the entire page. For internal navigation, always use <Link>.
Q: Do I need nested routes?
A: No, but they keep your code organized. For apps with 3–5 pages, flat routes are fine. For dashboards or complex apps, nested routes reduce repetition and improve maintainability.
Q: How do I pass data between routes?
A: Use URL parameters (/users/:id), query strings (/search?q=hello), or state management like Context or Redux. Avoid passing data through <Link state={} /> — it’s lost on refresh.
Q: Can I have multiple <Routes> in my app?
A: Yes. You can have multiple <Routes> blocks. The first matching route wins. Useful for layout-based routing: one <Routes> in the main layout, others in nested components.
Q: How do I handle protected routes (login)?
A: Create a <ProtectedRoute> wrapper that checks authentication before rendering:
jsx
function ProtectedRoute({ element }) {
const isAuthenticated = !!localStorage.getItem('auth_token')
return isAuthenticated ? element : <Navigate to="/login" />
}
// Usage:
<Route path="/dashboard" element={<ProtectedRoute element={<Dashboard />} />} />
Q: What’s the difference between useParams and useSearchParams?
A:
useParams— Dynamic route parameters:/users/:id→useParams().iduseSearchParams— Query strings:/search?q=coffee→useSearchParams().get('q')
Q: How do I update the page title for each route?
A: Use the useEffect hook in each component:
useEffect(() => {
document.title = 'Products | My App'
}, [])
Or use a library like react-helmet.
Q: Can I have routes with optional parameters?
A: Yes, using regex:
<Route path="/products/:id?" element={<Products />} />
// Matches both /products and /products/42
Q: How do I handle deep linking (sharing URLs)?
A: React Router handles this automatically. If you share /products/42, the user lands on that specific product. No special setup needed.
Q: What if I need to validate route parameters?
A: Validate in your component:
function ProductDetail() {
const { id } = useParams()
if (!id || isNaN(id)) {
return <Navigate to="/404" />
}
return <div>Product {id}</div>
}
Troubleshooting routing issues
“Cannot match any routes”
Problem: You visit /about, but the page shows blank.
Causes & fixes:
- Route path doesn’t match. Check spelling:
/aboutvs/About - Missing
<BrowserRouter>wrapper in main.jsx - Using
<a href="">instead of<Link to="">
Fix: Verify route paths are exact.
“useNavigate is not a function”
Problem: You used useNavigate() outside a component or before BrowserRouter.
Fix: Make sure component is inside <BrowserRouter>:
<BrowserRouter>
<App /> {/* useNavigate works here */}
</BrowserRouter>
“useParams returns undefined”
Problem: You defined /products/:id, but useParams().id is undefined.
Causes:
- Route path is
/products(no:id). Check spelling. - User visited wrong URL:
/products/instead of/products/42
Fix: Verify URL has the parameter.
“Links navigate but page doesn’t update”
Problem: URL changes, but component doesn’t re-render.
Cause: Component doesn’t depend on params/location.
Fix: Use useParams() or useLocation() inside component:
import { useParams, useLocation } from 'react-router-dom'
function MyComponent() {
const { id } = useParams() // Now component re-renders on param change
// ...
}
Next steps
You now know how to create routes and navigate between pages. Ready to level up?
Go back to the React + Vite setup guide and follow “What to build next” to:
- Render lists with React (complement your routing)
- Add forms and handle submissions
- Fetch data from APIs
- Style your routed pages
You’ll also want to understand how npm manages packages — React Router comes from npm, and as your app grows, you’ll install more routing packages.
Once you master routing, debugging with React DevTools becomes much more powerful. You can inspect route state, props, and see component re-renders as you navigate.
Conclusion
Routing is the difference between a single-page app and a multi-page experience. With React Router v6, creating routes is straightforward: install, wrap with BrowserRouter, define routes, add links, and navigate.
The key is remembering: <Link> for user-clickable navigation, useNavigate() for programmatic navigation, useParams() for accessing route data, and <Outlet> for nested route rendering.
Master these four concepts, and you can build any routing structure your app needs.
Last updated: January 2026. Information current as of React Router v6.20+, React 19, Node 20 LTS.