React Router Basics: Multi-Page React Apps
React Router Basics
~12 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026
Welcome to Phase 4: so far our app has had just a single "page". A real website needs multiple URLs, though – a product list, a detail page, a login area. React itself has no built-in system for this – we install the standard library React Router.
Installation
npm install react-router-domApp.jsx is getting messy – time to split it up
Our App.jsx now contains search, statistics, AND a review form on ONE single page. Right now, when we need several real sub-pages, is exactly the moment to move the current product list part out into its own file src/pages/ProductListPage.jsx.
Creating src/pages/ProductListPage.jsx
Create a new folder src/pages/ with this file:
import { useMemo, useRef, useState } from 'react';
import ProductCard from '../components/ProductCard';
const SAMPLE_PRODUCTS = [
{ sku: 'boots-01', name: 'Hiking Boots', price: 89.99, imageUrl: 'https://picsum.photos/seed/1/300' },
{ sku: 'backpack-01', name: 'Trekking Backpack', price: 59.5, imageUrl: 'https://picsum.photos/seed/2/300' },
{ sku: 'jacket-01', name: 'Rain Jacket', price: 74.0, imageUrl: 'https://picsum.photos/seed/3/300' },
];
function ProductListPage() {
const [query, setQuery] = useState('');
const searchInputRef = useRef(null);
const filtered = SAMPLE_PRODUCTS.filter((product) =>
product.name.toLowerCase().includes(query.toLowerCase())
);
const mostExpensiveProduct = useMemo(() => {
return filtered.reduce(
(max, p) => (p.price > (max?.price ?? 0) ? p : max),
null
);
}, [filtered]);
return (
<div>
<input
ref={searchInputRef}
type="text"
placeholder="Search products..."
value={query}
onChange={(event) => setQuery(event.target.value)}
/>
{mostExpensiveProduct && (
<p>Most expensive product: {mostExpensiveProduct.name} (${mostExpensiveProduct.price.toFixed(2)})</p>
)}
<div className="product-grid">
{filtered.map((product) => (
<ProductCard
key={product.sku}
name={product.name}
price={product.price}
imageUrl={product.imageUrl}
onSelect={() => alert(product.name + ' tapped')}
/>
))}
</div>
</div>
);
}
export default ProductListPage;Notice: no more useDocumentTitle, no more useAuth, no more <header> here – those belong in App.jsx, which from now on acts as a shared "layout" around ALL pages, no longer as the one page itself.
Cleaning up App.jsx: router and layout
React Router distinguishes three central building blocks: <BrowserRouter> (enables routing at all, goes in main.jsx), <Routes>/<Route> (define which URL shows which page), and <Link> (React Router's equivalent of <a href="..."> that does NOT reload the page).
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App.jsx';
import { AuthProvider } from './context/AuthContext.jsx';
import './index.css';
createRoot(document.getElementById('root')).render(
<StrictMode>
<BrowserRouter>
<AuthProvider>
<App />
</AuthProvider>
</BrowserRouter>
</StrictMode>
);import { Routes, Route } from 'react-router-dom';
import { useAuth } from './context/AuthContext';
import { useDocumentTitle } from './hooks/useDocumentTitle';
import ProductListPage from './pages/ProductListPage';
function App() {
const { user, login, logout } = useAuth();
useDocumentTitle('Product Catalog');
return (
<div className="app">
<header className="app-header">
<h1>Product Catalog</h1>
{user ? (
<p>Logged in as {user.username} <button onClick={logout}>Log out</button></p>
) : (
<button onClick={() => login('Jane Doe')}>Log in</button>
)}
</header>
<Routes>
<Route path="/" element={<ProductListPage />} />
</Routes>
</div>
);
}
export default App;Project structure after this chapter
produktkatalog-web/
├── index.html
├── package.json
├── vite.config.js
└── src/
├── main.jsx (updated: BrowserRouter)
├── App.jsx (cleaned up: layout + Routes)
├── index.css
├── context/
│ └── AuthContext.jsx
├── hooks/
│ └── useDocumentTitle.js
├── components/
│ ├── ProductCard.jsx
│ └── ReviewForm.jsx
└── pages/
└── ProductListPage.jsx ← NEW (content from old App.jsx)<Routes> looks at the current URL and renders exactly the <Route> whose path matches – path="/" represents the home page. Everything outside <Routes> (here: <header>) stays visible unchanged across every page switch – that's exactly what makes a "layout".
Achtung: The review form part from chapter 16 is now deliberately missing – we'll build it back in as its own ProductDetailPage.jsx sub-page, reachable via a dynamic route, in the next chapter, tied to a specific product instead of loose on the home page.