React Router
React handles rendering components, but it does not include a way to move between pages. React Router fills that gap. It lets you build multi-page apps that feel fast because the browser never does a full page reload when you navigate.
This page covers React Router v6, which is the current version. If you are maintaining an older project and see Switch or exact in the code, that is v5, which works differently.
Installing React Router
Start from an existing React project (see Starting a React Project if you need one), then install the package:
npm install react-router-dom
After installing, your package.json will include something like:
"dependencies": {
"react-router-dom": "^6.26.0"
}
Setting Up the Router
Wrap your app in BrowserRouter. You only do this once, usually at the top level in main.jsx:
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
createRoot(document.getElementById('root')).render(
<StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</StrictMode>
);
BrowserRouter uses the browser’s History API to keep the URL and the UI in sync. Everything inside it can use React Router’s hooks and components.
Defining Routes
Inside App.jsx, use Routes and Route to define which component renders at which path:
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;
Two things to notice compared to v5:
Routenow takes anelementprop instead of wrapping children.- There is no
exactkeyword. v6 matches exactly by default.
A 404 Page
Use path="*" as your last route to catch any URL that did not match:
import NotFound from './pages/NotFound';
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="*" element={<NotFound />} />
</Routes>
NotFound.jsx can be a simple component:
import { Link } from 'react-router-dom';
function NotFound() {
return (
<div>
<h1>404 - Page not found</h1>
<Link to="/">Go back home</Link>
</div>
);
}
export default NotFound;
Navigation with Link and NavLink
Use Link instead of <a href> for internal navigation. Link updates the URL without triggering a full page reload:
import { Link } from 'react-router-dom';
function Navbar() {
return (
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Link to="/contact">Contact</Link>
</nav>
);
}
Use NavLink when you want to style the active link differently. It automatically adds an active class when the link matches the current URL:
import { NavLink } from 'react-router-dom';
function Navbar() {
return (
<nav>
<NavLink to="/" end>Home</NavLink>
<NavLink to="/about">About</NavLink>
<NavLink to="/contact">Contact</NavLink>
</nav>
);
}
The end prop on the Home link prevents it from staying active on every page (since / is a prefix of all paths).
You can apply custom styles based on the active state:
<NavLink
to="/about"
style={({ isActive }) => ({ fontWeight: isActive ? 'bold' : 'normal' })}
>
About
</NavLink>
Programmatic Navigation
Use the useNavigate hook to navigate in response to events like a form submission or a button click:
import { useNavigate } from 'react-router-dom';
function LoginForm() {
const navigate = useNavigate();
const handleSubmit = (e) => {
e.preventDefault();
// ... handle login logic
navigate('/dashboard');
};
return (
<form onSubmit={handleSubmit}>
<button type="submit">Log in</button>
</form>
);
}
To go back one step in the browser history, pass -1:
navigate(-1);
URL Parameters
URL parameters let you create dynamic routes. For example, a blog post route where each post has its own slug.
Define the parameter in the route with a colon:
<Route path="/blog/:slug" element={<BlogPost />} />
Read the parameter inside the component with useParams:
import { useParams } from 'react-router-dom';
function BlogPost() {
const { slug } = useParams();
return <h1>Viewing post: {slug}</h1>;
}
If the user visits /blog/react-hooks, slug will be "react-hooks". You would typically use this value to fetch the matching post from an API.
Nested Routes
Nested routes are useful when part of the layout stays the same across multiple sub-pages. For example, a dashboard that always shows a sidebar, but changes the main content area.
Define the parent route and use Outlet inside it to render the matched child:
// App.jsx
import { Routes, Route } from 'react-router-dom';
import Dashboard from './pages/Dashboard';
import Overview from './pages/Overview';
import Settings from './pages/Settings';
function App() {
return (
<Routes>
<Route path="/dashboard" element={<Dashboard />}>
<Route index element={<Overview />} />
<Route path="settings" element={<Settings />} />
</Route>
</Routes>
);
}
// Dashboard.jsx
import { Outlet, Link } from 'react-router-dom';
function Dashboard() {
return (
<div>
<nav>
<Link to="/dashboard">Overview</Link>
<Link to="/dashboard/settings">Settings</Link>
</nav>
<main>
<Outlet />
</main>
</div>
);
}
<Outlet /> is where the child route renders. When the URL is /dashboard, the Overview component renders. When it is /dashboard/settings, the Settings component renders. The Dashboard layout (nav, sidebar, etc.) stays visible in both cases.
The index attribute on the first child route means it renders at the parent path exactly (no extra segment needed).
Complete Example
Here is a small app with a navbar, three pages, and a 404 page:
// main.jsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
createRoot(document.getElementById('root')).render(
<StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</StrictMode>
);
// App.jsx
import { Routes, Route } from 'react-router-dom';
import Navbar from './components/Navbar';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
import NotFound from './pages/NotFound';
function App() {
return (
<>
<Navbar />
<main>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route path="*" element={<NotFound />} />
</Routes>
</main>
</>
);
}
export default App;
// components/Navbar.jsx
import { NavLink } from 'react-router-dom';
function Navbar() {
return (
<nav>
<NavLink to="/" end>Home</NavLink>
<NavLink to="/about">About</NavLink>
<NavLink to="/contact">Contact</NavLink>
</nav>
);
}
export default Navbar;
// pages/Home.jsx
function Home() {
return <h1>Home</h1>;
}
export default Home;
// pages/NotFound.jsx
import { Link } from 'react-router-dom';
function NotFound() {
return (
<div>
<h1>404 - Page not found</h1>
<Link to="/">Go back home</Link>
</div>
);
}
export default NotFound;
FAQ
What changed from React Router v5 to v6?
Several things changed. Switch was replaced by Routes. The exact keyword was removed because v6 matches exactly by default. Routes now use the element prop instead of rendering children. useHistory was replaced by useNavigate. Nested routes work differently and use Outlet. If you are migrating from v5, the official migration guide
covers each change in detail.
How do I redirect programmatically?
Use the useNavigate hook inside a component or event handler. Call navigate('/some-path') to go to a new page, or navigate(-1) to go back. If you need a redirect at render time (for example, redirecting logged-out users), use the Navigate component:
import { Navigate } from 'react-router-dom';
function ProtectedPage({ isLoggedIn }) {
if (!isLoggedIn) {
return <Navigate to="/login" replace />;
}
return <h1>Welcome</h1>;
}
The replace prop replaces the current history entry instead of adding a new one, so the user cannot hit the back button to return to the protected page.
How do I pass data between routes?
There are a few options depending on the situation:
- URL params: for data that belongs in the URL, like an ID or slug. Use
useParamsto read it. - Query strings: for optional filters or search terms. Use
useSearchParamsto read and set them. - State via navigate: pass temporary data that should not appear in the URL:
navigate('/confirmation', { state: { orderId: '123' } });
Read it in the destination with useLocation:
import { useLocation } from 'react-router-dom';
function Confirmation() {
const { state } = useLocation();
return <p>Order ID: {state.orderId}</p>;
}
- Global state: for data shared across many pages, use React Context or a state management library.
See also