Routing with React Router

React · Practical · 14 min

Any multi-page app needs URLs that reflect what's on screen; React itself doesn't include a router.

React has no built-in router; the most widely used is React Router (v7, imported from react-router). It can run as a plain library in a Vite app or as a full framework with loaders and SSR. The library usage looks like this:

<BrowserRouter>
  <Routes>
    <Route path="/" element={<Layout />}>
      <Route index element={<Home />} />
      <Route path="users/:userId" element={<User />} />
      <Route path="*" element={<NotFound />} />
    </Route>
  </Routes>
</BrowserRouter>

Key ideas:

  • Routes match the URL and render their element. Paths can contain dynamic segments (:userId), read with useParams().
  • Nested routes build layouts. A parent route renders shared UI (header, sidebar) and an <Outlet /> where the matching child appears. index marks the child shown at the parent's own path.
  • Navigate without reloading using <Link to="/users/42"> or <NavLink> (which knows when it's active). Plain <a href> triggers a full page load.
  • Programmatic navigation after an action: const navigate = useNavigate(); navigate("/done").
  • Query strings via useSearchParams(), which is a good home for filter and sort state because it survives reloads and can be shared as a link.

React Router's data mode (createBrowserRouter with loader and action per route) and framework mode go further: data loads in parallel with code before rendering, avoiding effect waterfalls. Next.js and TanStack Router are popular alternatives with file-based or type-safe routing.

On a static host, configure the server to serve index.html for unknown paths, or direct links to /users/42 will 404.

Example

import { BrowserRouter, Routes, Route, Link, NavLink, Outlet, useParams, useNavigate } from "react-router";

function Layout() {
  return (
    <>
      <nav>
        <NavLink to="/">Home</NavLink> | <NavLink to="/users/1">User 1</NavLink>
      </nav>
      <main><Outlet /></main>   {/* matched child route renders here */}
    </>
  );
}

function Home() {
  return <p>Welcome. See <Link to="/users/2">user 2</Link>.</p>;
}

function User() {
  const { userId } = useParams();
  const navigate = useNavigate();
  return <><h2>User {userId}</h2><button onClick={() => navigate("/")}>Back home</button></>;
}

export default function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Layout />}>
          <Route index element={<Home />} />
          <Route path="users/:userId" element={<User />} />
          <Route path="*" element={<p>Not found</p>} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

Remember

  • Routes map URL patterns to elements; :param segments are read with useParams
  • Nested routes plus <Outlet /> give shared layouts
  • Use <Link>/useNavigate for client-side navigation; keep shareable UI state in the URL

Watch out for

  • Using <a href> for internal links, which reloads the whole app and loses state.

Check yourself

  1. In a nested route setup, where does the matching child route render?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: reactrouter.com/

Builds on

Unlocks

Nothing builds on this yet.