LearnFrenzy
Sign in Take a free test

Routing with React Router · Lesson 21 of 31

React Router Tutorial

6 min read Updated Nov 8, 2026 Saurabh Samir

A React app built with Vite is a single-page application: the browser loads one HTML file, and React swaps what's on screen. React Router maps URLs to components, so /courses and /about feel like separate pages – with working back and forward buttons and shareable links – without full page reloads.

In this lesson

  • Installing React Router and defining routes
  • Link and NavLink
  • Shared layouts with nested routes and Outlet
  • 404 pages, redirects and useNavigate

Install and define routes

npm install react-router

Since version 7, everything comes from the single react-router package (older tutorials import from react-router-dom). Wrap the app in BrowserRouter and list routes with Routes and Route:

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

function Home() {
  return <h1>Home</h1>;
}
function Courses() {
  return <h1>All courses</h1>;
}
function About() {
  return <h1>About LearnFrenzy</h1>;
}

export default function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">Home</Link> | <Link to="/courses">Courses</Link> | <Link to="/about">About</Link>
      </nav>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/courses" element={<Courses />} />
        <Route path="/about" element={<About />} />
      </Routes>
    </BrowserRouter>
  );
}

Result

URL: /
Home | Courses | About
Home

› click “Courses”
URL: /courses
Home | Courses | About
All courses

› click “About”
URL: /about
Home | Courses | About
About LearnFrenzy

› browser back
URL: /courses
Home | Courses | About
All courses

Clicking a Link changes the URL with the History API and renders the matching route – no request to the server. The browser's back button works because each navigation is a real history entry.

Use Link, not <a href>

A plain <a href="/courses"> makes the browser reload the whole page, losing all React state. Use <Link to> for internal pages and keep <a> for external sites.

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

function MenuLink({ to, label }) {
  return (
    <NavLink to={to} end className={({ isActive }) => (isActive ? "menu-link active" : "menu-link")}>
      {({ isActive }) => (isActive ? `▸ ${label}` : label)}
    </NavLink>
  );
}

export default function App() {
  return (
    <BrowserRouter>
      <nav>
        <MenuLink to="/" label="Dashboard" /> <MenuLink to="/progress" label="Progress" />
      </nav>
      <Routes>
        <Route path="/" element={<p>Welcome back!</p>} />
        <Route path="/progress" element={<p>12 of 31 lessons done</p>} />
      </Routes>
    </BrowserRouter>
  );
}

Result

URL: /
▸ Dashboard Progress
Welcome back!

› click “Progress”
URL: /progress
Dashboard ▸ Progress
12 of 31 lessons done

NavLink knows whether it matches the current URL. Its className, style and children can be functions that receive isActive; it also sets aria-current="page" for screen readers. The end prop stops / from matching every URL.

Nested routes and layouts

Most sites share a header and footer across pages. Make a layout route, and render <Outlet /> where the child page should appear:

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

function SiteLayout() {
  return (
    <div>
      <header>
        <strong>LearnFrenzy</strong> <Link to="/">Home</Link> <Link to="/learning">Tutorials</Link>
      </header>
      <main>
        <Outlet />   {/* the matching child route renders here */}
      </main>
      <footer>© 2026 LearnFrenzy</footer>
    </div>
  );
}

function NotFound() {
  return (
    <>
      <h1>404 – Page not found</h1>
      <Link to="/">Go home</Link>
    </>
  );
}

export default function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route element={<SiteLayout />}>
          <Route index element={<h1>Home</h1>} />                       {/* index = the parent's own URL */}
          <Route path="learning" element={<h1>Tutorials</h1>} />       {/* relative path → /learning */}
          <Route path="*" element={<NotFound />} />                    {/* anything else */}
        </Route>
      </Routes>
      <Link to="/does-not-exist">Broken link</Link>
    </BrowserRouter>
  );
}

Result

URL: /
LearnFrenzy Home Tutorials
Home
© 2026 LearnFrenzy
Broken link

› click “Tutorials”
URL: /learning
LearnFrenzy Home Tutorials
Tutorials
© 2026 LearnFrenzy
Broken link

› click “Broken link”
URL: /does-not-exist
LearnFrenzy Home Tutorials
404 – Page not found
Go home
© 2026 LearnFrenzy
Broken link

› click “Go home”
URL: /
LearnFrenzy Home Tutorials
Home
© 2026 LearnFrenzy
Broken link

Route tree

  • SiteLayout (header, Outlet, footer)
    • index → / → Home
    • learning → /learning → Tutorials
    • * → any other URL → NotFound
import { BrowserRouter, Routes, Route, Navigate, useNavigate } from "react-router";

function Checkout() {
  const navigate = useNavigate();
  function placeOrder() {
    // …save the order, then go to the confirmation page
    navigate("/orders/1042/thanks", { replace: true });   // replace: Back won't return to checkout
  }
  return (
    <>
      <h1>Checkout</h1>
      <button onClick={placeOrder}>Place order</button>
      <button onClick={() => navigate(-1)}>Back</button>
    </>
  );
}

export default function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Navigate to="/checkout" replace />} />   {/* redirect */}
        <Route path="/checkout" element={<Checkout />} />
        <Route path="/orders/:id/thanks" element={<h1>Thanks! Your order is confirmed.</h1>} />
      </Routes>
    </BrowserRouter>
  );
}

Result

URL: /checkout
Checkout
[Place order] [Back]

› click “Place order”
URL: /orders/1042/thanks
Thanks! Your order is confirmed.

The app opened at / and was redirected to /checkout straight away by <Navigate>. Use useNavigate after an action (a form submit, a login); use links for anything the user clicks to go somewhere.

Deploying a single-page app

If someone opens /learning directly, the server must return index.html so React Router can take over. Configure a fallback ("rewrite all to /index.html") on your host; otherwise refreshing any page except the home page gives a 404.

Practice

Try it yourself

  1. Add a "Contact" page and a footer link to it, inside the layout from the nested-routes example.

    Show solution
    import { BrowserRouter, Routes, Route, Link, Outlet } from "react-router";
    
    function Layout() {
      return (
        <>
          <Outlet />
          <footer><Link to="/contact">Contact us</Link></footer>
        </>
      );
    }
    
    export default function App() {
      return (
        <BrowserRouter>
          <Routes>
            <Route element={<Layout />}>
              <Route index element={<h1>Home</h1>} />
              <Route path="contact" element={<h1>Write to hello@learnfrenzy.com</h1>} />
            </Route>
          </Routes>
        </BrowserRouter>
      );
    }

    Result

    URL: /
    Home
    Contact us
    
    › click “Contact us”
    URL: /contact
    Write to hello@learnfrenzy.com
    Contact us

Quick check

Why use Link instead of <a href> for internal pages?

Link navigates without reloading the page, so React state is kept.

Where does a child route render inside a layout route?

Where the layout renders <Outlet />.

Which route path catches unknown URLs?

path="*".

Summary

  • BrowserRouter + Routes + Route map URLs to components; Link and NavLink navigate.
  • Layout routes render shared UI and an Outlet; index and * handle home and 404.
  • useNavigate and Navigate redirect in code; configure an index.html fallback when deploying.

Finished this lesson?

Mark it complete to track your progress, then carry on with the next lesson.

Next lesson

Popular