Dynamic Routes and Nested Routes in React Router
Dynamic Routes and Nested Routes
~13 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026
Now let's build a real product detail page – reachable via a URL like /products/boots-01, where "boots-01" is that product's SKU. That's a dynamic route. And we'll nest another sub-page for reviews right inside it – a nested route.
Dynamic routes: URL parameters
A colon before a path segment marks it as a placeholder ("parameter") that matches any value:
<Route path="/products/:sku" element={<ProductDetailPage />} />Inside ProductDetailPage, you read this value with the useParams() hook – comparable to route.params from our React Native tutorial, or a URL rewrite rule like /products/{{sku}} in classic PHP routers.
Creating src/pages/ProductDetailPage.jsx
Also new here: <Outlet />, a placeholder where React Router renders the matching NESTED route – here, for our reviews page:
import { Link, Outlet, useParams } from 'react-router-dom';
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 ProductDetailPage() {
const { sku } = useParams();
const product = SAMPLE_PRODUCTS.find((p) => p.sku === sku);
if (!product) {
return <p>Product not found.</p>;
}
return (
<div>
<img src={product.imageUrl} alt={product.name} />
<h2>{product.name}</h2>
<p>${product.price.toFixed(2)}</p>
<Link to={`/products/${sku}/reviews`}>See reviews</Link>
<Outlet />
</div>
);
}
export default ProductDetailPage;Creating src/pages/ReviewsPage.jsx (the nested page)
This page is where our ReviewForm from Phase 3 finally gets a proper, product-specific home:
import { useState } from 'react';
import { useParams } from 'react-router-dom';
import ReviewForm from '../components/ReviewForm';
function ReviewsPage() {
const { sku } = useParams();
const [reviews, setReviews] = useState([]);
function handleReviewSubmit(review) {
setReviews([...reviews, { ...review, id: Date.now() }]);
}
return (
<div>
<h3>Reviews for {sku}</h3>
<ReviewForm onSubmitReview={handleReviewSubmit} />
{reviews.length === 0 ? (
<p>No reviews yet.</p>
) : (
<ul>
{reviews.map((review) => (
<li key={review.id}>
<strong>{review.name}</strong> – {review.rating} stars
<p>{review.comment}</p>
</li>
))}
</ul>
)}
</div>
);
}
export default ReviewsPage;App.jsx: nesting the routes
Nested <Route> elements in JSX correspond to nested URLs – a <Route> as a CHILD of another gets rendered wherever the parent page placed its <Outlet />:
import { Routes, Route } from 'react-router-dom';
import { useAuth } from './context/AuthContext';
import { useDocumentTitle } from './hooks/useDocumentTitle';
import ProductListPage from './pages/ProductListPage';
import ProductDetailPage from './pages/ProductDetailPage';
import ReviewsPage from './pages/ReviewsPage';
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 />} />
<Route path="/products/:sku" element={<ProductDetailPage />}>
<Route path="reviews" element={<ReviewsPage />} />
</Route>
</Routes>
</div>
);
}
export default App;/products/boots-01 shows ProductDetailPage alone (the <Outlet /> stays empty). /products/boots-01/reviews shows THE EXACT SAME ProductDetailPage around it, but this time with ReviewsPage rendered into the <Outlet /> – the product info stays visible, only the lower part changes.
Updating ProductListPage.jsx: navigating with Link instead of alert
In src/pages/ProductListPage.jsx, replace the previous onSelect={{() => alert(...)}} with real navigation. ProductCard itself stays unchanged – we just pass it a different function:
import { useNavigate } from 'react-router-dom';
// ... inside the component:
const navigate = useNavigate();
// ... in the JSX, on each ProductCard:
onSelect={() => navigate(`/products/${product.sku}`)}useNavigate() returns a function you can use to switch to a new URL programmatically (from JavaScript code, e.g. after a click) – unlike <Link>, which is a clickable JSX element. Both approaches lead to the same navigation, just useful in different places in the code.
Project structure after this chapter (abbreviated)
produktkatalog-web/
└── src/
├── App.jsx (nested routes)
└── pages/
├── ProductListPage.jsx (updated: useNavigate)
├── ProductDetailPage.jsx ← NEW
└── ReviewsPage.jsx ← NEW (nested)