33 Application Layouts – Struktur statt Komponentenflut

Eine React-App ohne durchdachte Layout-Struktur wird schnell chaotisch. Jede Route dupliziert Header, Sidebar und Navigation. CSS-Grid-Definitionen wiederholen sich. Responsive Breakpoints sind inkonsistent. Menü-State wird in zehn verschiedenen Komponenten verwaltet. Das Ergebnis: Wartungshölle.

Professionelle Apps trennen Layout von Content. Ein Layout definiert die grundlegende Struktur (Header oben, Sidebar links, Content in der Mitte), während der Content variabel ist. React Router’s <Outlet /> macht diese Trennung elegant – Layouts werden zu wiederverwendbaren Containern für verschiedene Inhaltsseiten.

33.1 Das Problem: Duplizierter Layout-Code

Typischer Ansatz ohne Layout-Abstraktion:

// ❌ Dashboard.tsx - Alles in einer Komponente
function Dashboard() {
  return (
    <div>
      <header>
        <Logo />
        <Navigation />
        <UserMenu />
      </header>
      
      <aside>
        <Sidebar />
      </aside>
      
      <main>
        <h1>Dashboard</h1>
        {/* Dashboard content */}
      </main>
    </div>
  );
}

// ❌ Settings.tsx - Gleiche Struktur, duplizierter Code
function Settings() {
  return (
    <div>
      <header>
        <Logo />
        <Navigation />
        <UserMenu />
      </header>
      
      <aside>
        <Sidebar />
      </aside>
      
      <main>
        <h1>Settings</h1>
        {/* Settings content */}
      </main>
    </div>
  );
}

Probleme: - Header/Sidebar-Code ist 10x dupliziert - Änderungen müssen in jeder Datei gemacht werden - Inkonsistenzen schleichen sich ein - State (offene Sidebar, aktives Menü) muss überall synchronisiert werden

33.2 Layout-Komponenten: Die Lösung

Ein Layout ist eine Komponente, die die Struktur definiert, aber den Content als Kinder akzeptiert.

// layouts/MainLayout.tsx
import { ReactNode } from 'react';

interface MainLayoutProps {
  children: ReactNode;
}

export function MainLayout({ children }: MainLayoutProps) {
  return (
    <div className="main-layout">
      <header className="header">
        <Logo />
        <Navigation />
        <UserMenu />
      </header>
      
      <aside className="sidebar">
        <Sidebar />
      </aside>
      
      <main className="content">
        {children}
      </main>
    </div>
  );
}
// pages/Dashboard.tsx - Nur noch Content
export function Dashboard() {
  return (
    <MainLayout>
      <h1>Dashboard</h1>
      {/* Dashboard-spezifischer Content */}
    </MainLayout>
  );
}

// pages/Settings.tsx - Nutzt dasselbe Layout
export function Settings() {
  return (
    <MainLayout>
      <h1>Settings</h1>
      {/* Settings-spezifischer Content */}
    </MainLayout>
  );
}

Problem gelöst: Header und Sidebar existieren einmal, Content variiert.

33.3 React Router Integration: Outlet für Nested Routes

Noch besser: Layout als Route-Element mit <Outlet />. Outlet ist ein Platzhalter, den React Router mit der aktuellen Child-Route füllt.

// App.tsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { MainLayout } from './layouts/MainLayout';
import { Dashboard } from './pages/Dashboard';
import { Settings } from './pages/Settings';
import { Profile } from './pages/Profile';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        {/* Layout als Parent-Route */}
        <Route element={<MainLayout />}>
          {/* Child-Routes rendern in <Outlet /> */}
          <Route path="/dashboard" element={<Dashboard />} />
          <Route path="/settings" element={<Settings />} />
          <Route path="/profile" element={<Profile />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}
// layouts/MainLayout.tsx
import { Outlet } from 'react-router-dom';

export function MainLayout() {
  return (
    <div className="main-layout">
      <header className="header">
        <Logo />
        <Navigation />
        <UserMenu />
      </header>
      
      <aside className="sidebar">
        <Sidebar />
      </aside>
      
      <main className="content">
        <Outlet />  {/* Child-Route rendert hier */}
      </main>
    </div>
  );
}
// pages/Dashboard.tsx - Kein Layout-Wrapper mehr nötig!
export function Dashboard() {
  return (
    <>
      <h1>Dashboard</h1>
      {/* Content */}
    </>
  );
}

React Router ersetzt <Outlet /> automatisch mit der passenden Child-Komponente. Bei /dashboard<Dashboard />, bei /settings<Settings />.

33.4 Mehrere Layouts: Auth vs. Main vs. Admin

Verschiedene App-Bereiche brauchen verschiedene Layouts.

AuthLayout: Kein Header/Sidebar, zentrierter Content

// layouts/AuthLayout.tsx
import { Outlet } from 'react-router-dom';

export function AuthLayout() {
  return (
    <div className="auth-layout">
      <div className="auth-card">
        <Logo />
        <Outlet />
      </div>
    </div>
  );
}
/* AuthLayout Styling */
.auth-layout {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}

.auth-card {
  background: white;
  padding: 2rem;
  border-radius: 8px;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
  width: 100%;
  max-width: 400px;
}

AdminLayout: Extended Sidebar mit Admin-Navigation

// layouts/AdminLayout.tsx
import { Outlet } from 'react-router-dom';

export function AdminLayout() {
  return (
    <div className="admin-layout">
      <header className="header">
        <Logo />
        <h2>Admin Panel</h2>
        <UserMenu />
      </header>
      
      <aside className="admin-sidebar">
        <AdminNavigation />
      </aside>
      
      <main className="content">
        <Outlet />
      </main>
    </div>
  );
}

Route-Struktur mit verschiedenen Layouts:

// App.tsx
function App() {
  return (
    <BrowserRouter>
      <Routes>
        {/* Auth Routes - AuthLayout */}
        <Route element={<AuthLayout />}>
          <Route path="/login" element={<Login />} />
          <Route path="/register" element={<Register />} />
          <Route path="/forgot-password" element={<ForgotPassword />} />
        </Route>
        
        {/* Main App Routes - MainLayout */}
        <Route element={<MainLayout />}>
          <Route path="/dashboard" element={<Dashboard />} />
          <Route path="/products" element={<Products />} />
          <Route path="/settings" element={<Settings />} />
        </Route>
        
        {/* Admin Routes - AdminLayout */}
        <Route element={<AdminLayout />}>
          <Route path="/admin/users" element={<AdminUsers />} />
          <Route path="/admin/analytics" element={<AdminAnalytics />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}
Layout Routes Charakteristik
AuthLayout /login, /register Zentriert, kein Header/Sidebar
MainLayout /dashboard, /products Standard App-Layout
AdminLayout /admin/* Extended Sidebar, Admin-Branding

33.5 CSS Grid vs. Flexbox: Layout-Strategien

33.5.1 Grid-Based Layout: Fixed Regions

CSS Grid ist ideal für Layouts mit definierten Bereichen.

// layouts/MainLayout.tsx
export function MainLayout() {
  return (
    <div className="grid-layout">
      <header className="header">Header</header>
      <aside className="sidebar">Sidebar</aside>
      <main className="content">
        <Outlet />
      </main>
      <footer className="footer">Footer</footer>
    </div>
  );
}
.grid-layout {
  display: grid;
  grid-template-columns: 250px 1fr;
  grid-template-rows: 60px 1fr 60px;
  grid-template-areas:
    "header header"
    "sidebar content"
    "footer footer";
  min-height: 100vh;
}

.header {
  grid-area: header;
  background: white;
  border-bottom: 1px solid #e5e7eb;
  padding: 0 1rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.sidebar {
  grid-area: sidebar;
  background: #f9fafb;
  border-right: 1px solid #e5e7eb;
  padding: 1rem;
  overflow-y: auto;
}

.content {
  grid-area: content;
  padding: 2rem;
  overflow-y: auto;
}

.footer {
  grid-area: footer;
  background: #f9fafb;
  border-top: 1px solid #e5e7eb;
  padding: 0 1rem;
  display: flex;
  align-items: center;
}

/* Responsive: Sidebar ausblenden auf Mobile */
@media (max-width: 768px) {
  .grid-layout {
    grid-template-columns: 1fr;
    grid-template-areas:
      "header"
      "content"
      "footer";
  }
  
  .sidebar {
    display: none;
  }
}

Grid-Vorteile: - Klare Struktur durch benannte Areas - Einfaches Responsive-Verhalten - Perfekt für fixed-size Headers/Sidebars

33.5.2 Flexbox-Based Layout: Dynamic Sizing

Flexbox für flexiblere Layouts:

export function FlexLayout() {
  return (
    <div className="flex-layout">
      <header className="header">Header</header>
      
      <div className="body">
        <aside className="sidebar">Sidebar</aside>
        <main className="content">
          <Outlet />
        </main>
      </div>
      
      <footer className="footer">Footer</footer>
    </div>
  );
}
.flex-layout {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

.header {
  height: 60px;
  background: white;
  border-bottom: 1px solid #e5e7eb;
  flex-shrink: 0;
}

.body {
  display: flex;
  flex: 1;
}

.sidebar {
  width: 250px;
  background: #f9fafb;
  border-right: 1px solid #e5e7eb;
  flex-shrink: 0;
  overflow-y: auto;
}

.content {
  flex: 1;
  padding: 2rem;
  overflow-y: auto;
}

.footer {
  height: 60px;
  background: #f9fafb;
  border-top: 1px solid #e5e7eb;
  flex-shrink: 0;
}

Flexbox-Vorteile: - Natürliches Verhalten bei dynamischen Inhalten - Bessere Kontrolle über Overflow - Einfacher für vertikale Layouts

33.6 State-Management im Layout: Sidebar Toggle

Layouts brauchen oft State: Sidebar offen/geschlossen, aktives Menü-Item, User-Dropdown.

33.6.1 Context für Layout-State

// contexts/LayoutContext.tsx
import { createContext, useContext, useState, ReactNode } from 'react';

interface LayoutContextType {
  sidebarOpen: boolean;
  toggleSidebar: () => void;
  closeSidebar: () => void;
  openSidebar: () => void;
}

const LayoutContext = createContext<LayoutContextType | undefined>(undefined);

export function LayoutProvider({ children }: { children: ReactNode }) {
  const [sidebarOpen, setSidebarOpen] = useState(true);
  
  const toggleSidebar = () => setSidebarOpen(prev => !prev);
  const closeSidebar = () => setSidebarOpen(false);
  const openSidebar = () => setSidebarOpen(true);
  
  return (
    <LayoutContext.Provider value={{
      sidebarOpen,
      toggleSidebar,
      closeSidebar,
      openSidebar
    }}>
      {children}
    </LayoutContext.Provider>
  );
}

export function useLayout() {
  const context = useContext(LayoutContext);
  if (!context) {
    throw new Error('useLayout must be used within LayoutProvider');
  }
  return context;
}
// layouts/MainLayout.tsx
import { Outlet } from 'react-router-dom';
import { useLayout } from '../contexts/LayoutContext';
import { Menu } from 'lucide-react';

export function MainLayout() {
  const { sidebarOpen, toggleSidebar } = useLayout();
  
  return (
    <div className="main-layout">
      <header className="header">
        <button onClick={toggleSidebar} className="menu-button">
          <Menu />
        </button>
        <Logo />
        <UserMenu />
      </header>
      
      <div className="body">
        {sidebarOpen && (
          <aside className="sidebar">
            <Navigation />
          </aside>
        )}
        
        <main className="content">
          <Outlet />
        </main>
      </div>
    </div>
  );
}
// App.tsx
import { LayoutProvider } from './contexts/LayoutContext';

function App() {
  return (
    <BrowserRouter>
      <LayoutProvider>
        <Routes>
          <Route element={<MainLayout />}>
            <Route path="/dashboard" element={<Dashboard />} />
            <Route path="/settings" element={<Settings />} />
          </Route>
        </Routes>
      </LayoutProvider>
    </BrowserRouter>
  );
}

33.6.2 Responsive Sidebar: Overlay auf Mobile

Desktop: Sidebar immer sichtbar
Mobile: Sidebar als Overlay, schließt nach Navigation

// layouts/MainLayout.tsx
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { useLayout } from '../contexts/LayoutContext';
import { useMediaQuery } from '../hooks/useMediaQuery';

export function MainLayout() {
  const { sidebarOpen, toggleSidebar, closeSidebar } = useLayout();
  const location = useLocation();
  const isMobile = useMediaQuery('(max-width: 768px)');
  
  // Sidebar schließen bei Route-Wechsel auf Mobile
  useEffect(() => {
    if (isMobile) {
      closeSidebar();
    }
  }, [location.pathname, isMobile]);
  
  return (
    <div className={`main-layout ${sidebarOpen ? 'sidebar-open' : ''}`}>
      <header className="header">
        <button onClick={toggleSidebar}>
          <Menu />
        </button>
        <Logo />
        <UserMenu />
      </header>
      
      <div className="body">
        <aside className={`sidebar ${isMobile ? 'mobile' : 'desktop'}`}>
          <Navigation />
        </aside>
        
        {/* Backdrop auf Mobile wenn Sidebar offen */}
        {isMobile && sidebarOpen && (
          <div className="backdrop" onClick={closeSidebar} />
        )}
        
        <main className="content">
          <Outlet />
        </main>
      </div>
    </div>
  );
}
/* Desktop: Sidebar statisch */
.sidebar.desktop {
  position: static;
  width: 250px;
}

/* Mobile: Sidebar als Overlay */
.sidebar.mobile {
  position: fixed;
  top: 60px;
  left: 0;
  bottom: 0;
  width: 250px;
  background: white;
  transform: translateX(-100%);
  transition: transform 0.3s ease;
  z-index: 100;
  box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
}

.sidebar-open .sidebar.mobile {
  transform: translateX(0);
}

.backdrop {
  position: fixed;
  top: 60px;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.5);
  z-index: 99;
}
// hooks/useMediaQuery.ts
import { useState, useEffect } from 'react';

export function useMediaQuery(query: string): boolean {
  const [matches, setMatches] = useState(false);
  
  useEffect(() => {
    const media = window.matchMedia(query);
    
    setMatches(media.matches);
    
    const listener = (e: MediaQueryListEvent) => setMatches(e.matches);
    media.addEventListener('change', listener);
    
    return () => media.removeEventListener('change', listener);
  }, [query]);
  
  return matches;
}

Sidebar-Navigation sollte anzeigen, welche Route aktiv ist.

// components/Navigation.tsx
import { NavLink } from 'react-router-dom';
import { Home, Settings, Users, BarChart } from 'lucide-react';

export function Navigation() {
  return (
    <nav className="nav">
      <NavLink to="/dashboard" className="nav-item">
        <Home />
        <span>Dashboard</span>
      </NavLink>
      
      <NavLink to="/users" className="nav-item">
        <Users />
        <span>Users</span>
      </NavLink>
      
      <NavLink to="/analytics" className="nav-item">
        <BarChart />
        <span>Analytics</span>
      </NavLink>
      
      <NavLink to="/settings" className="nav-item">
        <Settings />
        <span>Settings</span>
      </NavLink>
    </nav>
  );
}
.nav {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.nav-item {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.75rem 1rem;
  border-radius: 6px;
  color: #6b7280;
  text-decoration: none;
  transition: all 0.2s;
}

.nav-item:hover {
  background: #f3f4f6;
  color: #111827;
}

/* React Router fügt .active automatisch hinzu */
.nav-item.active {
  background: #3b82f6;
  color: white;
}

.nav-item svg {
  width: 20px;
  height: 20px;
}

NavLink fügt automatisch .active CSS-Klasse hinzu, wenn die Route matched.

Für komplexere Active-Logic:

<NavLink 
  to="/users"
  className={({ isActive }) => 
    isActive ? 'nav-item active' : 'nav-item'
  }
>
  Users
</NavLink>

// Oder mit Custom Logic
<NavLink
  to="/users"
  className={({ isActive, isPending }) => {
    if (isPending) return 'nav-item loading';
    if (isActive) return 'nav-item active';
    return 'nav-item';
  }}
>
  Users
</NavLink>

33.8 Nested Layouts: Sub-Navigation in Settings

Manche Bereiche brauchen eigene Sub-Layouts.

// App.tsx
<Routes>
  <Route element={<MainLayout />}>
    <Route path="/dashboard" element={<Dashboard />} />
    
    {/* Settings mit eigenem Sub-Layout */}
    <Route path="/settings" element={<SettingsLayout />}>
      <Route index element={<Navigate to="profile" replace />} />
      <Route path="profile" element={<ProfileSettings />} />
      <Route path="security" element={<SecuritySettings />} />
      <Route path="notifications" element={<NotificationSettings />} />
    </Route>
  </Route>
</Routes>
// layouts/SettingsLayout.tsx
import { Outlet, NavLink } from 'react-router-dom';

export function SettingsLayout() {
  return (
    <div className="settings-layout">
      <h1>Settings</h1>
      
      <div className="settings-container">
        <nav className="settings-nav">
          <NavLink to="profile">Profile</NavLink>
          <NavLink to="security">Security</NavLink>
          <NavLink to="notifications">Notifications</NavLink>
        </nav>
        
        <div className="settings-content">
          <Outlet />
        </div>
      </div>
    </div>
  );
}
.settings-layout {
  max-width: 1200px;
  margin: 0 auto;
}

.settings-container {
  display: grid;
  grid-template-columns: 200px 1fr;
  gap: 2rem;
  margin-top: 2rem;
}

.settings-nav {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.settings-nav a {
  padding: 0.5rem 1rem;
  border-radius: 4px;
  color: #6b7280;
  text-decoration: none;
}

.settings-nav a:hover {
  background: #f3f4f6;
}

.settings-nav a.active {
  background: #eff6ff;
  color: #3b82f6;
  font-weight: 500;
}

.settings-content {
  background: white;
  padding: 2rem;
  border-radius: 8px;
  border: 1px solid #e5e7eb;
}

Hierarchie:

MainLayout (Header + Sidebar)
  └─ SettingsLayout (Settings Navigation)
      ├─ ProfileSettings
      ├─ SecuritySettings
      └─ NotificationSettings
// components/Breadcrumbs.tsx
import { useMatches, Link } from 'react-router-dom';
import { ChevronRight } from 'lucide-react';

interface RouteHandle {
  crumb?: (data?: any) => string;
}

export function Breadcrumbs() {
  const matches = useMatches();
  
  const crumbs = matches
    .filter(match => (match.handle as RouteHandle)?.crumb)
    .map(match => ({
      label: (match.handle as RouteHandle).crumb!(match.data),
      path: match.pathname
    }));
  
  return (
    <nav className="breadcrumbs">
      {crumbs.map((crumb, index) => (
        <span key={crumb.path}>
          {index > 0 && <ChevronRight className="separator" />}
          {index === crumbs.length - 1 ? (
            <span className="current">{crumb.label}</span>
          ) : (
            <Link to={crumb.path}>{crumb.label}</Link>
          )}
        </span>
      ))}
    </nav>
  );
}
// App.tsx mit handle-Prop
<Routes>
  <Route 
    element={<MainLayout />}
    handle={{ crumb: () => 'Home' }}
  >
    <Route 
      path="/products" 
      element={<Products />}
      handle={{ crumb: () => 'Products' }}
    />
    <Route 
      path="/products/:id" 
      element={<ProductDetail />}
      handle={{ crumb: (data) => data?.product?.name || 'Loading...' }}
    />
  </Route>
</Routes>
// layouts/MainLayout.tsx
import { Breadcrumbs } from '../components/Breadcrumbs';

export function MainLayout() {
  return (
    <div className="main-layout">
      <header className="header">
        {/* ... */}
      </header>
      
      <div className="body">
        <aside className="sidebar">
          <Navigation />
        </aside>
        
        <main className="content">
          <Breadcrumbs />
          <Outlet />
        </main>
      </div>
    </div>
  );
}

33.10 Protected Layouts: Auth-Check im Layout

Layout kann Auth-Check durchführen und redirecten.

// layouts/ProtectedLayout.tsx
import { Navigate, Outlet, useLocation } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';

export function ProtectedLayout() {
  const { user, loading } = useAuth();
  const location = useLocation();
  
  if (loading) {
    return <LoadingScreen />;
  }
  
  if (!user) {
    // Redirect zu Login, merke aktuelle Location
    return <Navigate to="/login" state={{ from: location }} replace />;
  }
  
  return <MainLayout />;
}
// App.tsx
<Routes>
  <Route element={<AuthLayout />}>
    <Route path="/login" element={<Login />} />
  </Route>
  
  {/* Protected Routes */}
  <Route element={<ProtectedLayout />}>
    <Route path="/dashboard" element={<Dashboard />} />
    <Route path="/settings" element={<Settings />} />
  </Route>
</Routes>

33.11 Role-Based Layouts: Admin vs. User

// layouts/RoleBasedLayout.tsx
import { Navigate, Outlet } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';
import { MainLayout } from './MainLayout';
import { AdminLayout } from './AdminLayout';

export function RoleBasedLayout() {
  const { user } = useAuth();
  
  if (!user) {
    return <Navigate to="/login" replace />;
  }
  
  // Admin bekommt AdminLayout
  if (user.role === 'admin') {
    return <AdminLayout />;
  }
  
  // Regular User bekommt MainLayout
  return <MainLayout />;
}
// App.tsx
<Route element={<RoleBasedLayout />}>
  <Route path="/dashboard" element={<Dashboard />} />
</Route>

33.12 Häufige Fehler

Fehler 1: Layout-State in jeder Komponente

// ❌ Falsch: Sidebar-State in jeder Page-Komponente
function Dashboard() {
  const [sidebarOpen, setSidebarOpen] = useState(true);
  
  return (
    <Layout sidebarOpen={sidebarOpen}>
      {/* ... */}
    </Layout>
  );
}

// ✓ Richtig: State im Layout selbst oder Context
function MainLayout() {
  const [sidebarOpen, setSidebarOpen] = useState(true);
  // ...
}

Fehler 2: Outlet vergessen

// ❌ Falsch: Kein Outlet in Layout
function MainLayout() {
  return (
    <div>
      <Header />
      <Sidebar />
      <main>
        {/* Wo soll Child-Route rendern? */}
      </main>
    </div>
  );
}

// ✓ Richtig: Outlet als Placeholder
function MainLayout() {
  return (
    <div>
      <Header />
      <Sidebar />
      <main>
        <Outlet />
      </main>
    </div>
  );
}

Fehler 3: Fixed Heights ohne Overflow

/* ❌ Falsch: Content kann nicht scrollen */
.content {
  height: calc(100vh - 60px);
}

/* ✓ Richtig: Overflow aktivieren */
.content {
  height: calc(100vh - 60px);
  overflow-y: auto;
}

Fehler 4: Zu viele Layout-Varianten

// ❌ Falsch: 10 verschiedene Layouts für minimale Unterschiede
<DashboardLayout />
<DashboardWithChartsLayout />
<DashboardWideLayout />
<DashboardNoSidebarLayout />

// ✓ Richtig: Ein flexibles Layout mit Props
<MainLayout 
  sidebar={<DashboardSidebar />}
  fullWidth={false}
/>

Fehler 5: Mobile-Responsive als Afterthought

/* ❌ Falsch: Sidebar bricht Layout auf Mobile */
.sidebar {
  width: 250px;
  /* Was passiert bei 320px Viewport? */
}

/* ✓ Richtig: Mobile-First oder klare Breakpoints */
.sidebar {
  width: 100%;
}

@media (min-width: 768px) {
  .sidebar {
    width: 250px;
  }
}

33.13 Zusammenfassung: Layout-Architektur

Element Zweck Technologie
Layout-Komponente Struktur-Container React Component
<Outlet /> Route-Content-Placeholder React Router
Layout Context Shared State (Sidebar, etc.) Context API
CSS Grid/Flexbox Visuelle Anordnung CSS
<NavLink> Navigation mit Active-State React Router
Breadcrumbs Hierarchie-Navigation React Router Matches
Protected Routes Auth-Check React Router + Context

Layouts sind das Fundament jeder professionellen React-App. Ein gut strukturiertes Layout-System spart nicht nur Entwicklungszeit – es macht die App wartbar, konsistent und skalierbar. Die Investment in durchdachte Layout-Komponenten zahlt sich aus, sobald die dritte Route hinzukommt.