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 without full page reloads has grown increasingly essential. React Router enables developers to define multiple views within a single-page app, map URLs to components, and
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 without full page reloads has grown increasingly essential. React Router enables developers to define multiple views within a single-page app, map URLs to components, and manage navigation stateall while maintaining a seamless user experience. Whether you're building a simple blog, an e-commerce platform, or a complex dashboard, mastering React Router is critical to delivering a responsive, intuitive interface.
Unlike traditional server-side routing, where each URL change triggers a full page load, React Router operates entirely on the client side. This means your applications state remains intact, transitions are faster, and users enjoy a more fluid interaction. With the latest versions of React Router (v6 and beyond), the API has been simplified, made more intuitive, and better aligned with Reacts component-based philosophy. This tutorial will guide you through everything you need to knowfrom installation and basic setup to advanced patterns, best practices, and real-world implementations.
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. For this guide, well use Vite, as its faster and more modern.
Open your terminal and run:
npm create vite@latest my-react-app -- --template react
Then navigate into the project directory and install dependencies:
cd my-react-app
npm install
Start the development server:
npm run dev
Once your app is running in the browser, youre ready to install React Router.
2. Installing React Router
React Router is distributed as a standalone package. Install it using npm or yarn:
npm install react-router-dom
This installs the DOM-specific bindings for React Router, which are required for web applications. If youre building a React Native app, youd use react-router-native instead.
3. Setting Up the Router
In React Router v6, the main entry point is the BrowserRouter component. This component wraps your entire app and provides routing context to all child components.
Open your main entry filetypically src/main.jsx or src/main.jsand wrap your root component with BrowserRouter:
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import { BrowserRouter } from 'react-router-dom'
ReactDOM.createRoot(document.getElementById('root')).render(
<BrowserRouter>
<App />
</BrowserRouter>
)
This single change enables routing throughout your application. All child components can now access routing utilities like useNavigate, useLocation, and useParams.
4. Creating Routes with Routes and Route
Now, define your applications routes inside your App.jsx file. Use the Routes and Route components to map URLs to components.
First, create a few basic pages. In your src folder, create three new files:
Home.jsxAbout.jsxContact.jsx
Heres what Home.jsx looks like:
import React from 'react'
const Home = () => {
return (
<div>
<h2>Home Page</h2>
<p>Welcome to the homepage of our React app.</p>
</div>
)
}
export default Home
Similarly, create About.jsx:
import React from 'react'
const About = () => {
return (
<div>
<h2>About Us</h2>
<p>Learn more about our mission and values.</p>
</div>
)
}
export default About
And Contact.jsx:
import React from 'react'
const Contact = () => {
return (
<div>
<h2>Contact Us</h2>
<p>Get in touch with our team via email or phone.</p>
</div>
)
}
export default Contact
Now, in your App.jsx, import these components and define your routes:
import React from 'react'
import { Routes, Route } from 'react-router-dom'
import Home from './Home'
import About from './About'
import Contact from './Contact'
const App = () => {
return (
<div>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
<Routes>
<Route path="/" element=<Home /> />
<Route path="/about" element=<About /> />
<Route path="/contact" element=<Contact /> />
</Routes>
</div>
)
}
export default App
Notice that we replaced the traditional <a> tags with React Routers Link component for better performance and behavior. Lets update the navigation:
import React from 'react'
import { Routes, Route, Link } from 'react-router-dom'
import Home from './Home'
import About from './About'
import Contact from './Contact'
const App = () => {
return (
<div>
<nav>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/contact">Contact</Link></li>
</ul>
</nav>
<Routes>
<Route path="/" element=<Home /> />
<Route path="/about" element=<About /> />
<Route path="/contact" element=<Contact /> />
</Routes>
</div>
)
}
export default App
The Link component prevents full page reloads and instead updates the URL and renders the corresponding component dynamically. This is the core benefit of client-side routing.
5. Navigating Programmatically with useNavigate
While Link is perfect for declarative navigation, sometimes you need to trigger navigation based on user actions like form submissions, button clicks, or API responses. For that, React Router provides the useNavigate hook.
Lets enhance the Contact component to include a button that redirects to the Home page after submission:
import React from 'react'
import { useNavigate } from 'react-router-dom'
const Contact = () => {
const navigate = useNavigate()
const handleSubmit = (e) => {
e.preventDefault()
// Simulate form submission
alert('Form submitted!')
navigate('/') // Redirect to home after submission
}
return (
<div>
<h2>Contact Us</h2>
<form onSubmit={handleSubmit}>
<input type="text" placeholder="Name" required />
<input type="email" placeholder="Email" required />
<textarea placeholder="Message" required />
<button type="submit">Send Message</button>
</form>
</div>
)
}
export default Contact
The useNavigate hook returns a function that accepts a path (string) or a navigation object. You can also pass options like { replace: true } to replace the current entry in the browser history instead of adding a new one.
6. Using URL Parameters with useParams
Dynamic routing allows you to capture parts of the URL as parameters. This is useful for displaying content based on identifierslike product IDs, user profiles, or blog posts.
Lets create a dynamic route for a product page. First, add a new component: Product.jsx:
import React from 'react'
import { useParams } from 'react-router-dom'
const Product = () => {
const { id } = useParams()
return (
<div>
<h2>Product Details</h2>
<p>You are viewing product: <strong>{id}</strong></p>
</div>
)
}
export default Product
Now, update your App.jsx to include a dynamic route:
import React from 'react'
import { Routes, Route, Link } from 'react-router-dom'
import Home from './Home'
import About from './About'
import Contact from './Contact'
import Product from './Product'
const App = () => {
return (
<div>
<nav>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/contact">Contact</Link></li>
<li><Link to="/product/123">View Product 123</Link></li>
</ul>
</nav>
<Routes>
<Route path="/" element=<Home /> />
<Route path="/about" element=<About /> />
<Route path="/contact" element=<Contact /> />
<Route path="/product/:id" element=<Product /> />
</Routes>
</div>
)
}
export default App
The :id in the path is a route parameter. When the user visits /product/123, the useParams hook extracts 123 and assigns it to the id variable. You can have multiple parameters: /user/:userId/post/:postId.
7. Nested Routes and Layout Components
Many applications have shared layoutslike headers, sidebars, or footersthat appear across multiple pages. React Router v6 supports nested routing, allowing you to render components within other components.
Lets create a layout component called Layout.jsx:
import React from 'react'
import { Outlet, Link } from 'react-router-dom'
const Layout = () => {
return (
<div>
<header>
<nav>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/contact">Contact</Link></li>
</ul>
</nav>
</header>
<main>
<Outlet />
</main>
<footer>
<p> 2024 My App. All rights reserved.</p>
</footer>
</div>
)
}
export default Layout
The <Outlet /> component is a placeholder that renders the child routes component. Now, update your App.jsx to use the layout:
import React from 'react'
import { Routes, Route } from 'react-router-dom'
import Layout from './Layout'
import Home from './Home'
import About from './About'
import Contact from './Contact'
import Product from './Product'
const App = () => {
return (
<Routes>
<Route path="/" element=<Layout />>
<Route index element=<Home /> />
<Route path="about" element=<About /> />
<Route path="contact" element=<Contact /> />
<Route path="product/:id" element=<Product /> />
</Route>
</Routes>
)
}
export default App
Notice the index prop on the Home route. This makes it the default child route when the parent path is matched (i.e., /).
Nested routing is especially powerful for dashboard interfaces, where a sidebar menu remains constant while the main content changes. You can nest routes as deeply as needed.
8. Handling 404 Pages with a Catch-All Route
Its essential to provide a user-friendly experience when a route doesnt exist. React Router allows you to define a catch-all route using an asterisk (*) as the path.
Create a NotFound.jsx component:
import React from 'react'
const NotFound = () => {
return (
<div style={{ padding: '40px', textAlign: 'center' }}>
<h2>404 - Page Not Found</h2>
<p>The page youre looking for doesnt exist.</p>
<Link to="/">Go Home</Link>
</div>
)
}
export default NotFound
Then, add it as the last route in your App.jsx:
<Routes>
<Route path="/" element=<Layout />>
<Route index element=<Home /> />
<Route path="about" element=<About /> />
<Route path="contact" element=<Contact /> />
<Route path="product/:id" element=<Product /> />
</Route>
<Route path="*" element=<NotFound /> />
</Routes>
The * route will match any URL that doesnt match the other defined routes, ensuring users never see a blank screen or browser error.
9. Using Query Parameters
Query parameters are key-value pairs appended to the URL after a question mark (e.g., ?category=electronics&sort=price). React Router doesnt parse them directly, but you can use the native URLSearchParams API to extract them.
Lets create a ProductsList.jsx component that filters products based on a query parameter:
import React from 'react'
import { useLocation } from 'react-router-dom'
const ProductsList = () => {
const location = useLocation()
const searchParams = new URLSearchParams(location.search)
const category = searchParams.get('category')
const sort = searchParams.get('sort')
return (
<div>
<h2>Products List</h2>
{category && <p>Filtered by category: <strong>{category}</strong></p>}
{sort && <p>Sorted by: <strong>{sort}</strong></p>}
<p>Displaying products based on query parameters.</p>
</div>
)
}
export default ProductsList
Add the route to App.jsx:
<Route path="/products" element=<ProductsList /> />
Now, visiting /products?category=electronics&sort=price will display the filtered results. You can also update query parameters programmatically using useNavigate:
const navigate = useNavigate()
navigate('/products?category=books')
Best Practices
1. Always Use Link Instead of Anchor Tags
While <a href="/about"> works, it causes a full page reload, defeating the purpose of a single-page application. Always use <Link to="/about"> from React Router for internal navigation. It ensures smooth transitions and preserves component state.
2. Avoid Deep Nesting Unless Necessary
While nested routes are powerful, over-nesting can make your route structure complex and harder to maintain. Only nest routes when you have a clear layout hierarchy (e.g., dashboard ? settings ? profile). Otherwise, keep routes flat.
3. Use Index Routes for Default Children
When using layout components, always define an index route for the default child. This makes your intent explicit and avoids confusion about which component renders at the root path.
4. Lazy Load Routes for Performance
Large applications can suffer from slow initial load times if all components are bundled together. Use Reacts lazy and Suspense to load routes on demand:
import { lazy, Suspense } from 'react'
import { Routes, Route } from 'react-router-dom'
const Home = lazy(() => import('./Home'))
const About = lazy(() => import('./About'))
const Contact = lazy(() => import('./Contact'))
const App = () => {
return (
<Routes>
<Route path="/" element=<Layout />>
<Route index element={
<Suspense fallback="Loading...">
<Home />
</Suspense>
} />
<Route path="about" element={
<Suspense fallback="Loading...">
<About />
</Suspense>
} />
<Route path="contact" element={
<Suspense fallback="Loading...">
<Contact />
</Suspense>
} />
</Route>
</Routes>
)
}
This reduces the initial JavaScript bundle size and improves load times, especially on mobile networks.
5. Handle Route Guards for Authentication
Many apps require protected routes. Create a custom component to check authentication status before rendering a route:
import React from 'react'
import { Navigate, Outlet } from 'react-router-dom'
const ProtectedRoute = ({ children }) => {
const isAuthenticated = localStorage.getItem('token') // or use context
if (!isAuthenticated) {
return <Navigate to="/login" replace />
}
return children ? children : <Outlet />
}
export default ProtectedRoute
Then wrap your protected routes:
<Route path="/dashboard" element=<ProtectedRoute><Dashboard /></ProtectedRoute> />
6. Use Relative Paths for Nested Routes
When defining child routes inside a layout, use relative paths. For example, if your parent route is /dashboard, define child routes as settings instead of /dashboard/settings. React Router automatically resolves the full path.
7. Test Your Routes
Use tools like React Testing Library to verify that routes render the correct components. Write tests for navigation, parameter extraction, and redirect behavior to ensure your routing logic is robust.
8. Keep URLs Clean and SEO-Friendly
Use semantic, readable paths like /blog/post-title instead of /post?id=123. This improves SEO and user trust. Avoid unnecessary parameters unless theyre essential for state.
Tools and Resources
Official Documentation
The official React Router documentation at reactrouter.com is comprehensive and regularly updated. It includes API references, code examples, migration guides, and video tutorials.
React Router DevTools
Install the React Router DevTools Chrome extension. It provides a visual representation of your route tree, current location, and parametersmaking debugging much easier.
CodeSandbox Templates
CodeSandbox offers pre-built templates for React Router. Search for React Router v6 to find starter projects you can fork and experiment with in real time.
React Router Examples Repository
The React Router team maintains a public GitHub repository with real-world examples: github.com/remix-run/react-router/examples. Explore examples like authentication, lazy loading, and nested layouts.
Linting and Type Safety
If youre using TypeScript, install the type definitions:
npm install --save-dev @types/react-router-dom
For linting, use ESLint with the eslint-plugin-react-router plugin to catch common routing mistakes like missing element props or invalid path syntax.
Analytics Integration
Track page views with tools like Google Analytics or Plausible. Use the useLocation hook to send events on route changes:
import { useEffect } from 'react'
import { useLocation } from 'react-router-dom'
const usePageViews = () => {
const location = useLocation()
useEffect(() => {
gtag('config', 'GA_MEASUREMENT_ID', {
page_path: location.pathname + location.search,
})
}, [location])
}
export default usePageViews
Call this hook in your root component to automatically log every route change.
Real Examples
Example 1: E-Commerce Product Catalog
Consider an online store with the following structure:
/Homepage with featured products/productsList of all products/products/:idIndividual product detail page/categories/:categoryFiltered product list by category/cartShopping cart/checkoutCheckout flow
Using React Router, you can build a seamless experience where users browse categories, click products, add to cart, and proceed to checkoutall without page reloads. You can also use query parameters to handle filters: /products?category=shoes&price=low.
With lazy loading, the cart and checkout modules are only loaded when the user navigates to them, improving initial performance.
Example 2: Admin Dashboard with Role-Based Access
An admin dashboard might have:
/loginPublic login page/dashboardMain dashboard (protected)/dashboard/usersManage users (admin only)/dashboard/settingsAccount settings/dashboard/reportsAnalytics (admin only)
Using protected routes and role checks (e.g., user.role === 'admin'), you can conditionally render menu items and restrict access. The layout component can display a sidebar that changes based on the users permissions.
Example 3: Blog with Markdown Posts
A blog might load posts dynamically from a CMS or local Markdown files. Each post has a URL like /blog/my-first-post.
Use useParams to extract the post slug, then fetch the corresponding Markdown file using a static import or API call:
import { useParams } from 'react-router-dom'
import { useEffect, useState } from 'react'
const BlogPost = () => {
const { slug } = useParams()
const [post, setPost] = useState(null)
useEffect(() => {
import(../posts/${slug}.md)
.then(module => setPost(module.default))
.catch(() => setPost(null))
}, [slug])
if (!post) return <p>Loading...</p>
return <div dangerouslySetInnerHTML={{ __html: post }} />
}
This approach allows you to manage content in Markdown files while maintaining clean, SEO-friendly URLs.
FAQs
What is the difference between React Router v5 and v6?
React Router v6 introduced a simplified API. Key changes include:
<Switch>was replaced with<Routes>- Routes now use the
elementprop instead ofcomponentorrender - Route matching is more precise and no longer requires exact matches
- Child routes are nested directly inside parent routes
useHistorybecameuseNavigateuseLocationanduseParamsremain unchanged
These changes make the API more intuitive and align it with Reacts functional component patterns.
Can I use React Router with server-side rendering (SSR)?
Yes, React Router supports SSR through frameworks like Next.js, Remix, or custom Node.js setups. In SSR, you must capture the initial URL on the server and render the matching route before sending the HTML to the client. React Router v6 works seamlessly with these frameworks.
How do I handle redirects in React Router?
Use the <Navigate /> component. For example:
<Route path="/old-page" element=<Navigate to="/new-page" replace /> />
You can also use it programmatically with useNavigate:
const navigate = useNavigate()
navigate('/new-page', { replace: true })
Can I use React Router without a build tool?
No. React Router relies on ES modules and a bundler like Webpack, Vite, or Rollup to function. It cannot be used directly in the browser via script tags without a build step.
How do I prevent multiple route matches?
React Router v6 matches routes in order. Place more specific routes before general ones. For example:
<Route path="/user/:id" element=<UserProfile /> />
<Route path="/user" element=<UserList /> />
If you reverse them, /user will match both /user and /user/123. Always order routes from most specific to least specific.
Does React Router support scroll restoration?
By default, React Router does not restore scroll position on navigation. To enable it, use the useScrollRestoration hook from react-router-dom (available in v6.4+):
import { useScrollRestoration } from 'react-router-dom'
const App = () => {
useScrollRestoration()
return <Routes>...</Routes>
}
This automatically saves and restores scroll position across route changes.
Is React Router compatible with TypeScript?
Yes. React Router has full TypeScript support. Install @types/react-router-dom and define route types explicitly for better autocompletion and error checking.
Conclusion
React Router is an indispensable tool for any developer building modern React applications. Its intuitive API, powerful features like nested routing and lazy loading, and seamless integration with Reacts ecosystem make it the go-to solution for client-side navigation. By following the steps outlined in this guidefrom basic setup to advanced patterns like route guards and dynamic importsyou now have the knowledge to implement robust, scalable routing in any project.
Remember that good routing isnt just about linking pagesits about crafting a coherent user journey. Clean URLs, fast transitions, meaningful error states, and performance optimizations all contribute to a better experience. Combine React Router with thoughtful UI design and youll create applications that feel native, responsive, and delightful to use.
As you continue building, revisit the official documentation, experiment with real-world examples, and dont hesitate to leverage community tools and plugins. React Router is constantly evolving, and staying updated ensures your applications remain performant and maintainable for years to come.