How to Use React Router
How to Use React Router React Router is the de facto standard for handling navigation and routing in React applications. As single-page applications (SPAs) have become the norm in modern web development, managing dynamic content transitions without full page reloads has grown essential. React Router enables developers to define multiple views within a single-page app and map them to specific URLs,
How to Use React Router
React Router is the de facto standard for handling navigation and routing in React applications. As single-page applications (SPAs) have become the norm in modern web development, managing dynamic content transitions without full page reloads has grown essential. React Router enables developers to define multiple views within a single-page app and map them to specific URLs, creating a seamless user experience that mimics traditional multi-page websiteswhile preserving the performance and interactivity benefits of React.
Whether you're building a simple blog, a complex e-commerce platform, or a dashboard with multiple authenticated sections, React Router provides the tools to manage routes, pass parameters, handle nested layouts, and protect routes based on user state. This tutorial will guide you through every critical aspect of using React Routerfrom installation to advanced patternsensuring you can build scalable, maintainable, and SEO-friendly React applications with confidence.
Step-by-Step Guide
1. Setting Up a React Project
Before you can use React Router, you need a React application. If you dont already have one, create a new project using Create React App (CRA) or Vite. Both are widely supported and beginner-friendly.
To create a project with Create React App, open your terminal and run:
npx create-react-app my-react-app
cd my-react-app
npm start
Alternatively, if you prefer a faster, more modern toolchain, use Vite:
npm create vite@latest my-react-app -- --template react
cd my-react-app
npm install
npm run dev
Once your project is running, youre ready to install React Router.
2. Installing React Router
React Router is distributed as a package on npm. To install the latest version (React Router v6 as of 2024), run:
npm install react-router-dom
This installs the DOM-specific bindings required for web applications. React Router is split into core and platform-specific packages, so react-router-dom includes everything you need for browser-based routing.
3. Setting Up the Router Provider
React Router works by wrapping your application in a router context. In React Router v6, the primary router for web apps is BrowserRouter. You need to wrap your root component with it.
Open src/index.js (or src/main.jsx if using Vite) and update it as follows:
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
This ensures that all child components can access routing functionality. Without this wrapper, hooks like useNavigate or useLocation will throw errors.
4. Creating Routes with Routes and Route
Now that the router is set up, you can define your applications routes. In React Router v6, the <Routes> component acts as a container for all your <Route> definitions. Each <Route> maps a URL path to a component.
Open src/App.js and replace its content with:
import { Routes, Route } from 'react-router-dom';
import Home from './components/Home';
import About from './components/About';
import Contact from './components/Contact';
function App() {
return (
<div className="App">
<Routes>
<Route path="/" element=<Home /> />
<Route path="/about" element=<About /> />
<Route path="/contact" element=<Contact /> />
</Routes>
</div>
);
}
export default App;
Each <Route> has two essential props:
path: The URL path that triggers the route.element: The React component to render when the path matches.
Notice that theres no longer a component prop as in v5. React Router v6 uses the element prop to accept a JSX element directly, enabling better support for props, hooks, and inline components.
5. Creating Navigation Links with Link
To allow users to navigate between routes, use the <Link> component. This component renders an anchor tag (<a>) but prevents full page reloads, making navigation smooth and fast.
Create a navigation bar in src/components/Navbar.js:
import { Link } from 'react-router-dom';
function Navbar() {
return (
<nav style={{ padding: '1rem', backgroundColor: '
f4f4f4', display: 'flex', gap: '1rem' }}>
<Link to="/" style={{ textDecoration: 'none', color: '333' }}>Home</Link>
<Link to="/about" style={{ textDecoration: 'none', color: '333' }}>About</Link>
<Link to="/contact" style={{ textDecoration: 'none', color: '333' }}>Contact</Link>
</nav>
);
}
export default Navbar;
Then import and use it in App.js:
import { Routes, Route } from 'react-router-dom';
import Home from './components/Home';
import About from './components/About';
import Contact from './components/Contact';
import Navbar from './components/Navbar';
function App() {
return (
<div className="App">
<Navbar />
<Routes>
<Route path="/" element=<Home /> />
<Route path="/about" element=<About /> />
<Route path="/contact" element=<Contact /> />
</Routes>
</div>
);
}
export default App;
Now, clicking on any link will update the URL and render the corresponding component without refreshing the page.
6. Dynamic Routing with URL Parameters
Many applications need to display content based on dynamic segments in the URLsuch as product IDs, user profiles, or blog post slugs. React Router supports this using route parameters.
Lets create a dynamic route for viewing individual blog posts. First, create a new component: src/components/BlogPost.js:
import { useParams } from 'react-router-dom';
function BlogPost() {
const { id } = useParams();
return (
<div>
<h2>Blog Post
{id}</h2>
<p>This is the detailed content for blog post with ID: {id}.</p>
</div>
);
}
export default BlogPost;
Then, update your App.js to include this route:
<Route path="/blog/:id" element=<BlogPost /> />
The :id is a route parameter. When a user visits /blog/123, the useParams hook will return an object like { id: '123' }.
You can also use multiple parameters:
<Route path="/user/:userId/post/:postId" element=<UserPost /> />
And access them with:
const { userId, postId } = useParams();
7. Navigating Programmatically with useNavigate
While <Link> is perfect for declarative navigation, sometimes you need to trigger navigation from JavaScriptlike after a form submission, API call, or conditional logic. For this, use the useNavigate hook.
Create a login form in src/components/Login.js:
import { useNavigate } from 'react-router-dom';
function Login() {
const navigate = useNavigate();
const handleLogin = () => {
// Simulate login success
setTimeout(() => {
navigate('/dashboard'); // Redirect after login
}, 1000);
};
return (
<div>
<h2>Login</h2>
<button onClick={handleLogin}>Log In</button>
</div>
);
}
export default Login;
The useNavigate hook returns a function that accepts a path as its first argument. You can also pass options:
navigate('/dashboard', { replace: true }): Replaces the current entry in the browser history instead of adding a new one.navigate(-1): Navigates back one step (equivalent to clicking the browsers back button).navigate(2): Navigates forward two steps.
This is especially useful for handling redirects after authentication, form submissions, or error recovery.
8. Nested Routes and Layout Components
Real-world applications often have layout structureslike headers, sidebars, or footersthat persist across multiple pages. React Router supports nested routing, allowing you to render components inside other components.
Lets create a dashboard layout with a sidebar and content area. First, create src/components/DashboardLayout.js:
import { Outlet } from 'react-router-dom';
function DashboardLayout() {
return (
<div style={{ display: 'flex', minHeight: '100vh' }}>
<aside style={{ width: '200px', backgroundColor: '
eee', padding: '1rem' }}>
<h3>Dashboard Menu</h3>
<ul style={{ listStyle: 'none', padding: 0 }}>
<li><Link to="overview" style={{ textDecoration: 'none', color: '
333' }}>Overview</Link></li>
<li><Link to="settings" style={{ textDecoration: 'none', color: '333' }}>Settings</Link></li>
</ul>
</aside>
<main style={{ flex: 1, padding: '1rem' }}>
<Outlet /> {/* This renders child routes */}
</main>
</div>
);
}
export default DashboardLayout;
The <Outlet> component is a placeholder that renders the child routes component. Now update your routes in App.js:
<Routes>
<Route path="/" element=<Home /> />
<Route path="/about" element=<About /> />
<Route path="/contact" element=<Contact /> />
<Route path="/dashboard" element=<DashboardLayout />>
<Route index element=<DashboardOverview /> />
<Route path="settings" element=<DashboardSettings /> />
</Route>
</Routes>
Notice the index prop on the first child route. This makes it the default route when the parent path is matched (e.g., /dashboard renders DashboardOverview).
This pattern allows you to reuse layouts across multiple routes without duplicating code. Its ideal for admin panels, user dashboards, or any section with shared UI elements.
9. Handling 404 Pages with a Catch-All Route
Its important to provide a user-friendly experience when a route doesnt exist. React Router lets you define a catch-all route using a wildcard path.
Create a NotFound.js component:
function NotFound() {
return (
<div style={{ textAlign: 'center', padding: '4rem' }}>
<h1>404 - Page Not Found</h1>
<p>The page youre looking for doesnt exist.</p>
<Link to="/">Go Home</Link>
</div>
);
}
export default NotFound;
Add it as the last route in your <Routes> block:
<Route path="*" element=<NotFound /> />
The * wildcard matches any path that hasnt been matched by previous routes. Since routes are evaluated in order, placing this at the end ensures it only triggers when no other route matches.
10. Query Parameters and Search Params
While route parameters are for path segments, query parameters (e.g., ?page=2&sort=asc) are used for filtering, pagination, or state that doesnt define the route itself.
React Router v6 provides the useSearchParams hook to manage query strings:
import { useSearchParams } from 'react-router-dom';
function ProductList() {
const [searchParams, setSearchParams] = useSearchParams();
const page = searchParams.get('page') || '1';
const sort = searchParams.get('sort') || 'name';
const handleSortChange = (newSort) => {
searchParams.set('sort', newSort);
setSearchParams(searchParams);
};
return (
<div>
<p>Current page: {page}</p>
<p>Sorted by: {sort}</p>
<button onClick={() => handleSortChange('price')}>Sort by Price</button>
</div>
);
}
useSearchParams returns an array: the first item is the current search parameters (a URLSearchParams object), and the second is a setter function to update them.
This approach keeps your UI state in the URL, making it shareable and bookmarkablea critical feature for SEO and user experience.
Best Practices
1. Always Use Relative Paths in Nested Routes
When defining child routes inside a layout component, use relative paths. For example:
<Route path="settings" element=<Settings /> />
instead of
<Route path="/dashboard/settings" element=<Settings /> />
Using relative paths makes your components more reusable. If you later decide to move the dashboard to /admin/dashboard, you wont need to update every child routejust the parent.
2. Lazy Load Routes for Better Performance
Large applications can suffer from slow initial loads due to large JavaScript bundles. To improve performance, use code splitting with Reacts lazy and Suspense features.
Modify your routes to load components on demand:
import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
const Home = lazy(() => import('./components/Home'));
const About = lazy(() => import('./components/About'));
const Contact = lazy(() => import('./components/Contact'));
function App() {
return (
<div className="App">
<Navbar />
<Suspense fallback=<div>Loading...</div>>
<Routes>
<Route path="/" element=<Home /> />
<Route path="/about" element=<About /> />
<Route path="/contact" element=<Contact /> />
</Routes>
</Suspense>
</div>
);
}
This ensures that only the code for the currently viewed route is downloaded, reducing initial load time and improving Lighthouse scores.
3. Avoid Inline Components in Route Elements
While you can write inline components like:
<Route path="/home" element=<div>Hello</div> />
its not recommended for production. Inline components are recreated on every render, which can cause unnecessary re-mounts and break optimizations like memoization or state persistence.
Always define components separately and import them. This improves maintainability and enables proper React DevTools debugging.
4. Use Index Routes for Default Child Content
When you have nested routes, always define an index route to render content at the parent path. Without it, navigating to /dashboard will render nothing.
Example:
<Route path="/dashboard" element=<DashboardLayout />>
<Route index element=<DashboardHome /> /> {/* Default */}
<Route path="profile" element=<Profile /> />
</Route>
5. Protect Routes with Authentication Guards
Many apps need to restrict access to certain pages. Create a reusable ProtectedRoute component:
import { Navigate } from 'react-router-dom';
import { useAuth } from './context/AuthContext';
function ProtectedRoute({ children }) {
const { user } = useAuth();
if (!user) {
return <Navigate to="/login" replace />;
}
return children;
}
Then wrap your protected routes:
<Route path="/dashboard" element=<ProtectedRoute><DashboardLayout /></ProtectedRoute> />
This keeps authentication logic centralized and reusable across routes.
6. Use useLocation to Track Page Views for Analytics
To track user navigation for tools like Google Analytics, listen to route changes using useLocation:
import { useLocation } from 'react-router-dom';
import { useEffect } from 'react';
function AnalyticsTracker() {
const location = useLocation();
useEffect(() => {
window.gtag('config', 'GA_MEASUREMENT_ID', {
page_path: location.pathname + location.search,
});
}, [location]);
return null;
}
Include this component inside your App component. It will fire every time the route changes, ensuring accurate tracking without manual event handling.
7. Avoid Hardcoding URLs
Instead of writing <Link to="/user/123/profile"> everywhere, create a helper function:
export const routes = {
home: '/',
user: (id) => /user/${id},
userProfile: (id) => /user/${id}/profile,
};
// Usage:
<Link to={routes.userProfile(123)}>Profile</Link>
This prevents broken links when URLs change and improves code maintainability.
Tools and Resources
Official Documentation
The React Router documentation is comprehensive and well-maintained. Always refer to it for API details, migration guides, and advanced patterns.
React Router DevTools
The React Router DevTools extension for Chrome and Firefox lets you inspect your route tree, current location, and route parameters in real timegreat for debugging complex nested routes.
Code Splitting with React.lazy
As mentioned earlier, combining React Router with React.lazy and Suspense is a best practice. Learn more about code splitting in the React documentation.
Testing Libraries
Use @testing-library/react-router to test your routed components. It provides utilities like render with a router context, making it easy to simulate navigation and verify component behavior.
Template Repositories
For quick project setup, explore these starter templates:
SEO Optimization Tools
Since React Router builds SPAs, ensure your routes are crawlable by search engines. Use tools like:
- Google Search Console to test rendered pages
- Screaming Frog to crawl your site and verify route structure
- Prerender.io for server-side rendering (SSR) if needed
Community Resources
Engage with the React Router community on:
Real Examples
Example 1: E-Commerce Product Catalog
Consider an online store with:
- Homepage (
/) - Product categories (
/categories/:category) - Individual product pages (
/products/:id) - Shopping cart (
/cart)
Implementation:
<Routes>
<Route path="/" element=<Home /> />
<Route path="/categories/:category" element=<CategoryPage /> />
<Route path="/products/:id" element=<ProductDetail /> />
<Route path="/cart" element=<Cart /> />
<Route path="*" element=<NotFound /> />
</Routes>
In ProductDetail, use useParams() to fetch product data from an API:
const { id } = useParams();
const [product, setProduct] = useState(null);
useEffect(() => {
fetch(/api/products/${id})
.then(res => res.json())
.then(data => setProduct(data));
}, [id]);
Use query parameters for filtering: /products?category=electronics&price=under50 with useSearchParams() to dynamically update the product list without changing the route.
Example 2: Admin Dashboard with Role-Based Access
An admin panel with:
- Login (
/login) - Dashboard (
/dashboard) - Users (
/dashboard/users) - Settings (
/dashboard/settings)
Use a ProtectedRoute wrapper:
<Route path="/login" element=<Login /> />
<Route path="/dashboard" element=<ProtectedRoute><DashboardLayout /></ProtectedRoute>>
<Route index element=<DashboardHome /> />
<Route path="users" element=<Users /> />
<Route path="settings" element=<Settings /> />
</Route>
In ProtectedRoute, check user roles:
if (!user) return <Navigate to="/login" />;
if (user.role !== 'admin' && location.pathname.includes('/dashboard/settings')) {
return <Navigate to="/dashboard" replace />;
}
This ensures users cant access restricted areas even if they bookmark the URL.
Example 3: Blog with SEO-Friendly URLs
For a blog, use clean, readable URLs like /blog/how-to-use-react-router.
Create a route:
<Route path="/blog/:slug" element=<BlogPost /> />
Fetch the post by slug:
const { slug } = useParams();
const [post, setPost] = useState(null);
useEffect(() => {
fetch(/api/posts?slug=${slug})
.then(res => res.json())
.then(data => setPost(data[0]));
}, [slug]);
Use useLocation to update meta tags dynamically:
import { useLocation } from 'react-router-dom';
import { useEffect } from 'react';
function SEOHead() {
const location = useLocation();
useEffect(() => {
document.title = post?.title || 'My Blog';
const metaDesc = document.querySelector('meta[name="description"]');
if (metaDesc) metaDesc.setAttribute('content', post?.excerpt || 'Read the latest blog posts.');
}, [location, post]);
return null;
}
Include <SEOHead /> in your layout to ensure each blog post has unique, crawlable metadata.
FAQs
Can I use React Router with server-side rendering (SSR)?
Yes, but React Router v6 is designed primarily for client-side rendering. For SSR, consider using frameworks like Remix (built on React Router) or Next.js, which handle routing and server rendering out of the box.
Whats the difference between BrowserRouter and HashRouter?
BrowserRouter uses the HTML5 History API and produces clean URLs like /about. HashRouter uses the URL hash () to simulate routing, e.g., /#about. Use BrowserRouter for modern apps. Use HashRouter only if youre deploying to a server that doesnt support clean URLs (e.g., static hosting without server config).
Why is my route not rendering?
Common causes:
- Missing
<BrowserRouter>wrapper around your app. - Incorrect path syntax (e.g., missing leading slash or extra slashes).
- Using
componentinstead ofelementin v6. - Route order: More specific routes should come before wildcards.
- Typo in component name or import path.
How do I handle redirects after login?
Use useNavigate() with replace: true to avoid users going back to the login page:
navigate('/dashboard', { replace: true });
Can I use React Router with TypeScript?
Absolutely. React Router has full TypeScript support. Install the types:
npm install --save-dev @types/react-router-dom
Then define types for route parameters:
const { id } = useParams<{ id: string }>();
How do I prevent duplicate route matching?
React Router v6 matches routes in order. Use exact only if needed (its no longer required). Ensure your routes are ordered from most specific to least specific. Always place the wildcard * route at the end.
Does React Router affect SEO?
By itself, React Router doesnt hurt SEO, but SPAs can if not handled properly. Ensure:
- Each route renders unique, meaningful content.
- Meta tags (title, description) are updated per route.
- Your server or CDN serves pre-rendered HTML for crawlers (e.g., via SSR or static site generation).
- You submit a sitemap with all your routes to search engines.
Conclusion
React Router is not just a navigation libraryits the backbone of modern React applications. By mastering its core conceptsroutes, links, parameters, nested layouts, and programmatic navigationyou gain the ability to build complex, user-friendly, and scalable SPAs with precision and confidence.
From simple static sites to enterprise-grade dashboards, React Router adapts to your needs. When combined with best practices like code splitting, route protection, SEO optimization, and testing, it becomes a powerful tool in your developer toolkit.
Remember: the key to effective routing is clarity. Organize your routes logically, reuse layouts, avoid hardcoding URLs, and always test your navigation paths. As your application grows, your routing structure should remain intuitive and maintainable.
Now that youve learned how to use React Router from the ground up, start building. Experiment with nested layouts, dynamic parameters, and query strings. Explore the official examples. Contribute to the community. And most importantlybuild something amazing.