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

Oct 30, 2025 - 13:11
Oct 30, 2025 - 13:11
 0

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.jsx
  • About.jsx
  • Contact.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
  • /products List of all products
  • /products/:id Individual product detail page
  • /categories/:category Filtered product list by category
  • /cart Shopping cart
  • /checkout Checkout 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:

  • /login Public login page
  • /dashboard Main dashboard (protected)
  • /dashboard/users Manage users (admin only)
  • /dashboard/settings Account settings
  • /dashboard/reports Analytics (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 element prop instead of component or render
  • Route matching is more precise and no longer requires exact matches
  • Child routes are nested directly inside parent routes
  • useHistory became useNavigate
  • useLocation and useParams remain 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.