Skip to content
React rc routing 4 min read

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.

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 (or NavLink) 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 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.

PropComponentPurpose
toLink, NavLinkTarget path or location object
replaceLink, NavLinkReplace the history entry instead of pushing
stateLink, NavLinkPass non-URL data to the destination
endNavLinkRequire an exact match for isActive
classNameNavLinkString, or ({ isActive, isPending }) => string
relativeLink, 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 end prop to root (to="/") NavLinks so they are not active on every route.
  • Prefer the createBrowserRouter object 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 isActive instead of comparing useLocation() by hand.
Last updated June 14, 2026
Was this helpful?