What Is React Router and How Does Client-Side Routing Work?

Intermediate10 min interview
Skills tested:
Explaining how client-side routing works without a full page reloadUsing BrowserRouter, Routes, Route, Link, and Outlet correctlyReading dynamic URL segments with useParamsNavigating programmatically with useNavigateKnowing the server configuration requirement for direct URL access

Advertisement

🧩 Scenario

Client-side routing is a foundational SPA concept. Interviewers expect you to explain the History API mechanism, demonstrate correct Route and Link usage, and know the deployment gotcha that catches developers on static hosts.

Architecture Walkthrough

How the History API Enables Client-Side Routing

Traditional navigation sends an HTTP request to the server and receives a new HTML document. Client-side routing intercepts that navigation before it reaches the server. The browser's History API exposes pushState and replaceState, which update the URL and add an entry to the history stack without triggering an HTTP request. React Router wraps this API: when a user clicks a <Link>, React Router calls pushState, the URL changes, and React re-renders the matching component. No HTML is fetched from the server. Subsequent navigations are pure JavaScript.

The back and forward buttons still work because they step through the history stack, triggering the popstate event, which React Router listens to and uses to re-render the correct component. From the user's perspective, navigation feels identical to a server-rendered site, but the page never reloads.

The Server Configuration Requirement

Client-side routing creates a deployment trap. The server has no knowledge of /dashboard/orders as a route because the route only exists in the JavaScript bundle. When a user navigates directly to /dashboard/orders (by typing it in, refreshing, or following a link from another site), the browser makes an HTTP request to the server for that path. If the server is not configured to return index.html for all routes, it returns a 404 because the file at /dashboard/orders does not exist on disk. The fix is a catch-all rule on the server: serve index.html for any path that does not match a static file, and let React Router handle routing in the browser.

Nested Routes and Layout Composition

React Router v6 nested routes make shared layouts straightforward. A parent route renders a layout component that includes navigation and structure. The <Outlet /> component inside the layout marks where the matched child route renders. Only the content area swaps when navigating between child routes; the layout stays mounted and avoids re-rendering.


Key Code Explained

import {
  BrowserRouter,
  Routes,
  Route,
  Link,
  Outlet,
  useParams,
  useNavigate,
  Navigate,
} from 'react-router-dom';

// Root router setup
function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/products">Products</Link>
      </nav>

      <Routes>
        <Route path="/" element={<HomePage />} />
        <Route path="/products" element={<ProductListPage />} />
        {/* :id is a URL parameter — useParams reads it */}
        <Route path="/products/:id" element={<ProductDetailPage />} />

        {/* Redirect old URL to new one */}
        <Route path="/shop" element={<Navigate to="/products" replace />} />

        {/* 404 catch-all */}
        <Route path="*" element={<NotFoundPage />} />
      </Routes>
    </BrowserRouter>
  );
}

// Reading URL parameters
function ProductDetailPage() {
  const { id } = useParams<{ id: string }>();
  const { data: product, isLoading } = useProduct(id!);

  if (isLoading) return <Skeleton />;
  return <h1>{product?.name}</h1>;
}


// Nested routes: shared layout with Outlet
function DashboardLayout() {
  return (
    <div className="dashboard">
      <aside>
        <Link to="/dashboard">Overview</Link>
        <Link to="/dashboard/orders">Orders</Link>
        <Link to="/dashboard/settings">Settings</Link>
      </aside>
      <main>
        {/* Matched child route renders here */}
        <Outlet />
      </main>
    </div>
  );
}

function AppWithDashboard() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/dashboard" element={<DashboardLayout />}>
          {/* index matches /dashboard exactly */}
          <Route index element={<Overview />} />
          <Route path="orders" element={<Orders />} />
          <Route path="settings" element={<Settings />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}


// Programmatic navigation
function SearchBar() {
  const navigate = useNavigate();
  const [query, setQuery] = useState('');

  const handleSearch = (e: React.FormEvent) => {
    e.preventDefault();
    // Navigate to a new URL — adds to history stack
    navigate(`/search?q=${encodeURIComponent(query)}`);
  };

  return (
    <form onSubmit={handleSearch}>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <button type="submit">Search</button>
    </form>
  );
}

<Link to="/products"> renders an <a> tag but intercepts the click event and calls pushState instead of following the href. This is the critical difference from a plain <a href="/products">, which would cause a full page reload. Using a regular anchor tag is the most common beginner mistake.


Tradeoffs

Router typeURL formatServer config requiredSEOBest for
BrowserRouter/products/1Yes (catch-all for index.html)GoodProduction SPAs
HashRouter/#/products/1No (hash never sent to server)PoorStatic hosts without config access
MemoryRouterNo URL changeNoN/ATests; embedded apps

What Interviewers Actually Check

  • Whether you can explain pushState and why the page does not reload
  • Whether you know the server configuration requirement for direct URL access
  • Whether you use Link over anchor tags and can explain why
  • Whether you know nested routes and Outlet for layout composition
  • Whether you can distinguish index routes from path="/" and explain the difference

Follow-Up Questions

  1. How does React Router v6 handle scroll restoration when navigating between pages?
  2. How do you implement code splitting with React.lazy and Suspense in a React Router v6 route config?
  3. How do you pass data from one route to another without putting it in the URL? What are the tradeoffs compared to URL state?
  4. How does React Router determine which route wins when two routes could both match the current path?
  5. How do you test a component that uses useNavigate or useParams in React Testing Library?

Common Candidate Mistakes

  • Using <a href="/page"> instead of <Link to="/page"> — the anchor tag causes a full page reload
  • Not configuring the server to serve index.html for all paths — direct URL access returns a 404 in production
  • Calling useParams or useNavigate in a component that is not inside a <BrowserRouter> — throws a runtime error
  • Nesting routes in the JSX without a parent layout route, then wondering why the child component renders in the wrong place
  • Using path="/" for the dashboard index route instead of the index attribute — path="/" matches the root, not the parent route

Interview Readiness Checklist

Before you leave this question, make sure you can answer:

  • Can you explain how pushState enables navigation without a page reload?
  • Can you set up a basic router with BrowserRouter, Routes, Route, and Link?
  • Can you create a nested layout route with <Outlet /> and a shared sidebar?
  • Can you read a dynamic URL segment with useParams and navigate programmatically with useNavigate?
  • Can you explain the server config requirement and name the alternative for hosts without config access?

Summary

React Router uses the browser's History API (pushState) to update the URL and re-render matching components without an HTTP request. <Link> replaces anchor tags and intercepts clicks before they trigger navigation. Routes are matched by path specificity. Nested routes with <Outlet /> enable shared layouts where only the content area re-renders on navigation. useParams reads dynamic segments from the URL, and useNavigate handles programmatic navigation. The key production concern is server configuration: the server must return index.html for every path that does not match a static file, or direct URL access will return a 404. HashRouter avoids this requirement but at the cost of URL aesthetics and SEO.

Frequently Asked Questions

What is the difference between BrowserRouter and HashRouter?

BrowserRouter uses the HTML5 History API and produces clean URLs like /products/1. It requires the server to return index.html for all routes. HashRouter uses the URL hash (#) so paths look like /#/products/1. The hash is never sent to the server, so no special server config is needed. HashRouter is simpler to deploy but produces uglier URLs and has SEO limitations.

Advertisement


Stay Updated

Get the latest frontend challenges, interview questions and tutorials delivered to your inbox.

Advertisement