Routes & Links
A router does two jobs: it maps the current URL to the components that should render, and it lets users move between those URLs without reloading the page. In React Router the first job is handled by your route definitions, and the second by the Link and NavLink components. Getting both right is what turns a pile of components into a navigable application that feels instant and still respects the browser’s back button, bookmarks, and refresh.
Defining routes
The most common way to declare routes is with the Routes and Route components rendered inside your tree. Routes looks at the current location, picks the single best-matching Route, and renders its element. Each Route pairs a URL path with the component to show.
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import NotFound from './pages/NotFound';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}
export default App;
The path="*" route is a catch-all that matches anything not matched above, which makes it the idiomatic place for a 404 page. Matching is exact by default in React Router 6/7, so /about will not accidentally match /about/team.
Route objects and the data router
The same configuration can be expressed as plain JavaScript objects and passed to createBrowserRouter. This object form is required to use the data APIs (loaders, actions, deferred data), and it is the recommended approach for new apps.
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
const router = createBrowserRouter([
{ path: '/', element: <Home /> },
{ path: '/about', element: <About /> },
{ path: '*', element: <h1>404 Not Found</h1> },
]);
export default function App() {
return <RouterProvider router={router} />;
}
Both styles describe the same thing. The JSX form is convenient for small apps; the object form scales better and unlocks data loading, covered in the data router page.
Link vs. anchor tags
It is tempting to write <a href="/about">About</a>, but a plain anchor triggers a full-page navigation: the browser throws away the current document, refetches index.html, and reboots your entire React app. You lose in-memory state, scroll position, and the snappy feel of a single-page app.
Link renders a real <a> element for accessibility and SEO, but intercepts the click and updates the URL through the History API instead, so React Router swaps components in place.
import { Link } from 'react-router-dom';
function Nav() {
return (
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Link to="/products?sort=price">Products</Link>
</nav>
);
}
Use
Link(orNavLink) for every internal navigation. Reserve plain<a href>for external URLs, mailto links, and file downloads where you genuinely want the browser to take over.
NavLink and active styling
NavLink is a Link that knows whether its target matches the current URL. Instead of plain values, its className and style props accept a function that receives { isActive, isPending }, letting you style the link for the page the user is currently on.
import { NavLink } from 'react-router-dom';
function MainNav() {
return (
<nav>
<NavLink
to="/"
end
className={({ isActive }) => (isActive ? 'link active' : 'link')}
>
Home
</NavLink>
<NavLink
to="/about"
style={({ isActive }) => ({
fontWeight: isActive ? 'bold' : 'normal',
})}
>
About
</NavLink>
</nav>
);
}
The end prop is critical on the root link. Without it, to="/" is considered active for every route, because every path begins with /. Adding end requires an exact match.
Output:
<!-- when the user is on /about, the rendered HTML is: -->
<a href="/" class="link">Home</a>
<a href="/about" style="font-weight: bold;">About</a>
Index routes
When a parent route renders a layout with an <Outlet />, you often want a default child to show at the parent’s exact path. That default is an index route — a child with index instead of a path.
<Routes>
<Route path="/dashboard" element={<DashboardLayout />}>
<Route index element={<DashboardHome />} />
<Route path="settings" element={<Settings />} />
</Route>
</Routes>
Here, /dashboard renders DashboardLayout with DashboardHome inside the outlet, while /dashboard/settings renders Settings in the same slot. An index route is the routing equivalent of a folder’s index.html.
Link prop reference
| Prop | Component | Purpose |
|---|---|---|
to | Link, NavLink | Target path or location object |
replace | Link, NavLink | Replace the history entry instead of pushing |
state | Link, NavLink | Pass non-URL data to the destination |
end | NavLink | Require an exact match for isActive |
className | NavLink | String, or ({ isActive, isPending }) => string |
relative | Link, NavLink | "route" (default) or "path" resolution |
Best Practices
- Always navigate internally with
Link/NavLink; only use<a href>for external or non-app destinations. - Add the
endprop to root (to="/")NavLinks so they are not active on every route. - Prefer the
createBrowserRouterobject form for new apps to keep the door open for loaders and actions. - Keep a
path="*"catch-all route so unknown URLs render a friendly 404 instead of a blank screen. - Use index routes for a layout’s default content rather than duplicating the parent path.
- Derive active styles from
isActiveinstead of comparinguseLocation()by hand.