React course menu
React Router Tutorial: Pages, Links and URL Parameters
Add multiple pages to a React app with React Router: installing it, BrowserRouter, Routes and Route, Link and NavLink, URL parameters with useParams, navigating in code with useNavigate, query strings with useSearchParams, layouts with Outlet, and 404 pages.
On this page
A React app built with Vite has a single HTML page. Routing lets it show different "pages" for different URLs (/, /products, /products/3) without reloading: the URL changes, the back button works, and React swaps the components on screen. React Router is the standard library for this.
Install and set up#
npm install react-routerWrap the app in a BrowserRouter, which connects React Router to the browser's address bar:
// src/main.jsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter } from "react-router";
import App from "./App.jsx";
createRoot(document.getElementById("root")).render(
<StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</StrictMode>,
);Older tutorials import from react-router-dom; current versions use the single react-router package.
A shop with four routes#
// src/App.jsx
import { Link, NavLink, Outlet, Route, Routes, useNavigate, useParams, useSearchParams } from "react-router";
const PRODUCTS = [
{ id: "1", name: "Bamboo mat", price: 1299 },
{ id: "2", name: "Cotton kurta", price: 899 },
{ id: "3", name: "Silk saree", price: 4599 },
];
function Home() {
return <h1>Welcome to ShopKart</h1>;
}
function Products() {
const [searchParams, setSearchParams] = useSearchParams();
const sort = searchParams.get("sort") ?? "name";
const sorted = PRODUCTS.toSorted((a, b) => (sort === "price" ? a.price - b.price : a.name.localeCompare(b.name)));
return (
<div>
<h1>Products</h1>
<button onClick={() => setSearchParams({ sort: "price" })}>Sort by price</button>
<ul>
{sorted.map((p) => (
<li key={p.id}>
<Link to={`/products/${p.id}`}>{p.name}</Link>
</li>
))}
</ul>
</div>
);
}
function ProductDetail() {
const { id } = useParams();
const navigate = useNavigate();
const product = PRODUCTS.find((p) => p.id === id);
if (!product) return <h1>Product not found</h1>;
return (
<div>
<h1>{product.name}</h1>
<p>₹{product.price}</p>
<button onClick={() => navigate("/products")}>Back to products</button>
</div>
);
}
function NotFound() {
return <h1>404: Page not found</h1>;
}
function Layout() {
return (
<>
<nav>
<NavLink to="/">Home</NavLink> | <NavLink to="/products">Products</NavLink>
</nav>
<main>
<Outlet />
</main>
<footer>© 2026 ShopKart</footer>
</>
);
}
export default function App() {
return (
<Routes>
<Route element={<Layout />}>
<Route index element={<Home />} />
<Route path="products" element={<Products />} />
<Route path="products/:id" element={<ProductDetail />} />
<Route path="*" element={<NotFound />} />
</Route>
</Routes>
);
}What each URL shows (checked by the tests):
The nav bar and footer appear on every page, because all routes are nested inside the Layout route.
The pieces#
Routes and Route map URL paths to elements. React Router picks the best match, so products/:id and the catch-all * do not conflict with products.
Nested routes and Outlet: a route without a path (here Layout) wraps its child routes. The matching child renders where the layout puts <Outlet />. Layouts are how you share headers, sidebars and footers. index marks the child shown at the parent's own URL (/).
Link changes the URL without reloading the page. Use it instead of <a href> for links inside your app; a normal <a> reloads everything and loses state.
NavLink is a Link that knows when it matches the current URL and then gets the class active, so you can style the current menu item:
nav a.active { font-weight: bold; text-decoration: underline; }URL parameters: :id in a path captures that part of the URL. useParams() returns them as strings: on /products/3, id is "3", which is why the product ids above are strings too.
useNavigate navigates from code: after a form submits, after logging in, or from a button, as in "Back to products". navigate(-1) goes back one step in history.
useSearchParams reads and writes the query string (?sort=price), which is ideal for filters, sorting and search terms: the state lives in the URL, so it survives reloads and can be shared as a link.
Deploying a client-side router#
On a static host, a user who reloads /products/3 asks the server for a file at that path, which does not exist. Configure the host to serve index.html for every path (often called a "single-page app fallback" or "rewrite rule"), and React Router then shows the right page.
Beyond the basics#
React Router also has a data mode (createBrowserRouter with loader and action functions that load data before a route renders and handle form submissions) and a framework mode with server rendering. Next.js uses file-based routing instead; see React Frameworks.
Common mistakes#
Practice#
Practice exercise
Add an "About" page at /about to the shop, with a nav link to it, and make the product detail page show a "Product not found" message with a link back to the list when the id does not exist.
Show hint
Add <Route path="about" element={<About />} /> inside the layout route and a <NavLink to="/about">. In ProductDetail, return a fragment containing the message and <Link to="/products">.
Show solution
function About() {
return <h1>About ShopKart</h1>;
}
// in Layout's <nav>:
<NavLink to="/about">About</NavLink>
// in App's <Route element={<Layout />}>:
<Route path="about" element={<About />} />
// in ProductDetail:
if (!product) {
return (
<>
<h1>Product not found</h1>
<Link to="/products">See all products</Link>
</>
);
}Key takeaway#
Next: Testing React Components.
Finished this lesson?