Topic 11 / 14

React Router — Multi-Page Apps

~10 min read  //  React Series  //  Coding India

Client-Side Routing

A React app is one HTML page, but users expect URLs: /courses, /courses/django, shareable and bookmarkable. React Router swaps components based on the URL without reloading:

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

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/courses" element={<CourseList />} />
        <Route path="/courses/:slug" element={<CourseDetail />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}

Links — Never <a> Internally

import { Link, NavLink } from "react-router-dom";

<Link to="/courses">Courses</Link>

// NavLink knows when it's active — perfect for navbars
<NavLink
  to="/courses"
  className={({ isActive }) => (isActive ? "nav-link active" : "nav-link")}
>
  Courses
</NavLink>

A plain <a href> triggers a full page reload and throws away all state. Link intercepts the click and just changes the route.

URL Parameters

import { useParams } from "react-router-dom";

function CourseDetail() {
  const { slug } = useParams();              // from /courses/:slug
  const { data: course, isLoading } = useFetch(`/api/courses/${slug}`);

  if (isLoading) return <Spinner />;
  if (!course) return <NotFound />;
  return <h1>{course.title}</h1>;
}

Query strings have their own hook:

const [searchParams, setSearchParams] = useSearchParams();
const page = Number(searchParams.get("page") ?? 1);
setSearchParams({ page: page + 1 });        // updates ?page= in the URL

State in the URL is shareable, bookmarkable, and survives refresh — prefer it over useState for filters, tabs, and pagination.

Nested Routes & Layouts

Share a navbar/footer across pages with a layout route and Outlet:

import { Outlet } from "react-router-dom";

function Layout() {
  return (
    <>
      <Navbar />
      <main><Outlet /></main>     {/* the matched child renders here */}
      <Footer />
    </>
  );
}

<Routes>
  <Route element={<Layout />}>
    <Route path="/" element={<Home />} />
    <Route path="/courses" element={<CourseList />} />
  </Route>
</Routes>

Programmatic Navigation & Protected Routes

import { useNavigate, Navigate } from "react-router-dom";

// after an action
const navigate = useNavigate();
async function handleLogin(form) {
  await login(form);
  navigate("/dashboard");
}

// gate a route on auth
function RequireAuth({ children }) {
  const { user } = useAuth();
  if (!user) return <Navigate to="/login" replace />;
  return children;
}

<Route path="/dashboard" element={<RequireAuth><Dashboard /></RequireAuth>} />

That’s the complete routing toolkit: declarative routes, params, layouts, redirects. (Next.js, in its own series, bakes routing into the filesystem — same concepts, different ergonomics.)