Quick Answer

React Router swaps components based on the URL without reloading the page. Use Link rather than an anchor tag, or you lose all your state. On deployment, configure the server to serve index.html for every path or refreshing a route returns 404.

What client-side routing means

A traditional site asks the server for a new HTML document on every navigation. A single page app loads one document and then swaps what is displayed, updating the address bar to match.

The benefit is speed — no full reload, no white flash, and state such as a logged-in user or a filled form survives navigation. The cost is that you now own routing, and the browser's default behaviour has to be intercepted.

npm install react-router-dom

The basic setup

import { BrowserRouter, Routes, Route, Link } from "react-router-dom";

function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/about">About</Link>
      </nav>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}

BrowserRouter wraps the app and watches the URL. Routes picks the first matching Route and renders its element. The * path is the catch-all — add it from the start, because without one an unknown URL renders nothing at all and looks like a broken app.

This is the most common beginner mistake and its symptom is confusing.

<a href="/about">About</a>      {/* wrong */}
<Link to="/about">About</Link>  {/* right */}

An anchor tag triggers a full browser navigation. The page reloads, React restarts, and every piece of state is lost — the user is logged out of the UI, the form is cleared, the cart empties. It looks like it works, just slower and with mysterious state loss.

Link intercepts the click, updates the URL through the History API and re-renders. No reload, no state loss.

Use NavLink instead when you want to style the active item — it applies an active class automatically, which saves comparing the current path by hand.

Route parameters and programmatic navigation

<Route path="/user/:id" element={<UserPage />} />
import { useParams, useNavigate } from "react-router-dom";

function UserPage() {
  const { id } = useParams();
  return <h1>User {id}</h1>;
}

function LoginForm() {
  const navigate = useNavigate();
  async function onSubmit() {
    await login();
    navigate("/dashboard");
  }
}

useParams reads the :id segment. Note it is always a string — comparing it to a numeric ID with === fails silently, which is a genuinely common bug.

useNavigate moves programmatically, for redirecting after login or after a form submits. For query strings such as ?page=2, use useSearchParams, which works much like React state.

The 404-on-refresh problem

Everything works locally. You deploy, click through to /about, refresh, and get a 404. The app is not broken — this is a server configuration issue and it catches almost everyone once.

When you click a Link, the server is never contacted; React handles it. When you refresh, the browser genuinely requests /about from the server, which looks for a file at that path, finds none, and returns 404.

The fix is to tell the server to serve index.html for any path it does not recognise, letting React Router take over once the app loads. On Netlify, a _redirects file containing /* /index.html 200. On Apache, a rewrite rule in .htaccess. On nginx, try_files $uri /index.html.

Most hosts that advertise single page app support do this automatically. See deploying your project for free for the wider set of first-deployment problems.

Frequently Asked Questions

Why does my page reload and lose state when I click a link? You used an anchor tag instead of Link. An anchor causes a full browser navigation, restarting React and discarding all state. Replace it with Link from react-router-dom.
Why do I get a 404 when I refresh a route? The server is looking for a real file at that path. Configure it to serve index.html for unknown paths so React Router can handle routing once the app loads.
How do I redirect after login? Use the useNavigate hook and call navigate('/dashboard') once the login completes. For redirecting during render, the Navigate component is the appropriate choice.
How do I protect a route so only logged-in users see it? Wrap the element in a component that checks authentication and renders Navigate to the login page otherwise. Remember this is only a UI convenience — the server must still enforce authorisation.
Is React Router part of React? No, it is a separate library installed with npm. React itself has no built-in routing, which is why frameworks like Next.js provide their own.