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

React Router Grundlagen: Mehrseitige React-Apps

React Router Grundlagen

~12 Min. Lesezeit Zuletzt aktualisiert am 8. August 2026

Willkommen in Phase 4: Bisher hatte unsere App nur eine einzige "Seite". Eine echte Website braucht aber mehrere URLs – eine Produktliste, eine Detailseite, einen Login-Bereich. React selbst bringt dafür kein eingebautes System mit – wir installieren die Standard-Bibliothek React Router.

Installation

npm install react-router-dom

App.jsx wird unübersichtlich – Zeit zum Aufteilen

Unsere App.jsx enthält inzwischen Suche, Statistik UND Bewertungsformular auf EINER einzigen Seite. Genau jetzt, wo wir mehrere echte Unterseiten brauchen, lagern wir den bisherigen Produktlisten-Teil in eine eigene Datei src/pages/ProductListPage.jsx aus.

src/pages/ProductListPage.jsx anlegen

Legen Sie einen neuen Ordner src/pages/ mit dieser Datei an:

src/pages/ProductListPage.jsx
import { useMemo, useRef, useState } from 'react';
import ProductCard from '../components/ProductCard';

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 ProductListPage() {
  const [query, setQuery] = useState('');
  const searchInputRef = useRef(null);

  const gefiltert = SAMPLE_PRODUCTS.filter((produkt) =>
    produkt.name.toLowerCase().includes(query.toLowerCase())
  );

  const teuerstesProdukt = useMemo(() => {
    return gefiltert.reduce(
      (max, p) => (p.price > (max?.price ?? 0) ? p : max),
      null
    );
  }, [gefiltert]);

  return (
    <div>
      <input
        ref={searchInputRef}
        type="text"
        placeholder="Produkt suchen..."
        value={query}
        onChange={(event) => setQuery(event.target.value)}
      />
      {teuerstesProdukt && (
        <p>Teuerstes Produkt: {teuerstesProdukt.name} ({teuerstesProdukt.price.toFixed(2)} €)</p>
      )}
      <div className="product-grid">
        {gefiltert.map((produkt) => (
          <ProductCard
            key={produkt.sku}
            name={produkt.name}
            price={produkt.price}
            imageUrl={produkt.imageUrl}
            onSelect={() => alert(produkt.name + ' angeklickt')}
          />
        ))}
      </div>
    </div>
  );
}

export default ProductListPage;

Beachten Sie: kein useDocumentTitle, kein useAuth, keine <header> mehr hier – die gehören gleich in App.jsx, das ab jetzt als gemeinsames "Layout" um ALLE Seiten herum fungiert, nicht mehr als die eine Seite selbst.

App.jsx aufräumen: Router und Layout

React Router unterscheidet drei zentrale Bausteine: <BrowserRouter> (aktiviert Routing überhaupt, kommt in main.jsx), <Routes>/<Route> (definieren, welche URL welche Seite zeigt), und <Link> (das React-Router-Äquivalent zu <a href="...">, das die Seite NICHT neu lädt).

src/main.jsx
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>
);
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';

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 />} />
      </Routes>
    </div>
  );
}

export default App;

Projektstruktur nach diesem Kapitel

produktkatalog-web/
├── index.html
├── package.json
├── vite.config.js
└── src/
    ├── main.jsx                     (aktualisiert: BrowserRouter)
    ├── App.jsx                      (aufgeräumt: Layout + Routes)
    ├── index.css
    ├── context/
    │   └── AuthContext.jsx
    ├── hooks/
    │   └── useDocumentTitle.js
    ├── components/
    │   ├── ProductCard.jsx
    │   └── ReviewForm.jsx
    └── pages/
        └── ProductListPage.jsx      ← NEU (Inhalt aus altem App.jsx)

<Routes> schaut sich die aktuelle URL an und rendert genau die <Route>, deren path passt – path="/" steht für die Startseite. Alles außerhalb von <Routes> (hier: <header>) bleibt bei JEDEM Seitenwechsel gleich sichtbar – genau das macht ein "Layout" aus.

Achtung: Der Bewertungsformular-Teil aus Kapitel 16 fehlt jetzt bewusst – wir bauen ihn im nächsten Kapitel als eigene, per Dynamic Route erreichbare Unterseite ProductDetailPage.jsx wieder ein, passend zu einem konkreten Produkt statt lose auf der Startseite.