Dynamic Routes und Nested Routes in React Router
Dynamic Routes und Nested Routes
~13 Min. Lesezeit Zuletzt aktualisiert am 8. August 2026
Jetzt bauen wir eine echte Produktdetailseite – erreichbar über eine URL wie /products/schuhe-01, bei der "schuhe-01" die SKU des jeweiligen Produkts ist. Das ist eine Dynamic Route. Und wir verschachteln direkt eine weitere Unterseite für Bewertungen darin – eine Nested Route.
Dynamic Routes: URL-Parameter
Ein Doppelpunkt vor einem Pfadsegment markiert es als Platzhalter ("Parameter"), der zu einem beliebigen Wert passt:
<Route path="/products/:sku" element={<ProductDetailPage />} />Innerhalb von ProductDetailPage lesen Sie diesen Wert mit dem Hook useParams() aus – vergleichbar mit route.params aus unserem React-Native-Tutorial, oder mit einer URL-Rewrite-Regel wie /products/{{sku}} in klassischen PHP-Routern.
src/pages/ProductDetailPage.jsx anlegen
Neu ist auch <Outlet />: ein Platzhalter, an dem React Router die passende VERSCHACHTELTE Route hineinrendert – hier gleich für unsere Bewertungsseite:
import { Link, Outlet, useParams } from 'react-router-dom';
const SAMPLE_PRODUCTS = [
{ sku: 'schuhe-01', name: 'Wanderschuhe', price: 89.99, imageUrl: 'https://picsum.photos/seed/1/300' },
{ sku: 'rucksack-01', name: 'Trekking-Rucksack', price: 59.5, imageUrl: 'https://picsum.photos/seed/2/300' },
{ sku: 'jacke-01', name: 'Regenjacke', price: 74.0, imageUrl: 'https://picsum.photos/seed/3/300' },
];
function ProductDetailPage() {
const { sku } = useParams();
const produkt = SAMPLE_PRODUCTS.find((p) => p.sku === sku);
if (!produkt) {
return <p>Produkt nicht gefunden.</p>;
}
return (
<div>
<img src={produkt.imageUrl} alt={produkt.name} />
<h2>{produkt.name}</h2>
<p>{produkt.price.toFixed(2)} €</p>
<Link to={`/products/${sku}/reviews`}>Bewertungen ansehen</Link>
<Outlet />
</div>
);
}
export default ProductDetailPage;src/pages/ReviewsPage.jsx anlegen (die verschachtelte Seite)
Diese Seite ist der Ort, an dem unser ReviewForm aus Phase 3 endlich einen passenden, produktspezifischen Platz bekommt:
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>Bewertungen für {sku}</h3>
<ReviewForm onSubmitReview={handleReviewSubmit} />
{reviews.length === 0 ? (
<p>Noch keine Bewertungen.</p>
) : (
<ul>
{reviews.map((review) => (
<li key={review.id}>
<strong>{review.name}</strong> – {review.rating} Sterne
<p>{review.comment}</p>
</li>
))}
</ul>
)}
</div>
);
}
export default ReviewsPage;App.jsx: die Routen verschachteln
Verschachtelte <Route>-Elemente in JSX entsprechen verschachtelten URLs – eine <Route> als KIND einer anderen wird dort gerendert, wo die Eltern-Seite ihr <Outlet /> platziert hat:
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('Produktkatalog');
return (
<div className="app">
<header className="app-header">
<h1>Produktkatalog</h1>
{user ? (
<p>Angemeldet als {user.username} <button onClick={logout}>Abmelden</button></p>
) : (
<button onClick={() => login('Max Mustermann')}>Anmelden</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/schuhe-01 zeigt ProductDetailPage allein (der <Outlet /> bleibt leer). /products/schuhe-01/reviews zeigt GENAU DIESELBE ProductDetailPage drumherum, aber diesmal mit ReviewsPage im <Outlet /> gerendert – die Produktinfo bleibt sichtbar, nur der untere Teil wechselt.
ProductListPage.jsx aktualisieren: mit Link statt Alert navigieren
Ersetzen Sie in src/pages/ProductListPage.jsx das bisherige onSelect={{() => alert(...)}} durch echte Navigation. ProductCard selbst bleibt unverändert – wir übergeben ihr nur eine andere Funktion:
import { useNavigate } from 'react-router-dom';
// ... innerhalb der Komponente:
const navigate = useNavigate();
// ... im JSX, bei jeder ProductCard:
onSelect={() => navigate(`/products/${produkt.sku}`)}useNavigate() gibt eine Funktion zurück, mit der Sie programmatisch (aus JavaScript-Code heraus, z. B. nach einem Klick) zu einer neuen URL wechseln – im Unterschied zu <Link>, das ein klickbares JSX-Element ist. Beide Wege führen zur selben Navigation, nur an unterschiedlichen Stellen im Code sinnvoll.
Projektstruktur nach diesem Kapitel (gekürzt)
produktkatalog-web/
└── src/
├── App.jsx (verschachtelte Routes)
└── pages/
├── ProductListPage.jsx (aktualisiert: useNavigate)
├── ProductDetailPage.jsx ← NEU
└── ReviewsPage.jsx ← NEU (verschachtelt)