Routing with React Router · Lesson 21 of 31
React Router Tutorial
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
LinkandNavLink- 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>
);
}
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.
NavLink: highlight the current page
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>
);
}
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>
);
}
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
Navigating in code: useNavigate and Navigate
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>
);
}
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
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> ); }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.