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

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:

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

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>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 />:

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('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.

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)