Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

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:

src/pages/ProductDetailPage.jsx
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:

src/pages/ReviewsPage.jsx
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:

src/App.jsx
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.

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)