Skip to main content

🏗️ Lesson 6.5: Layout Routes

Build professional application structures with shared layouts using the Outlet component. Master nested routes, create persistent navigation elements, and design multi-layout architectures that scale with your application's complexity.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Understand and implement layout routes with the Outlet component
  • Create shared layouts with persistent navigation and headers
  • Build nested route structures with parent-child relationships
  • Design multi-layout applications with different UI patterns
  • Implement dynamic breadcrumb navigation
  • Create sidebar layouts with nested navigation
  • Handle layout-specific styling and behavior
  • Build professional, scalable application architectures

Estimated Time: 75-90 minutes

Prerequisites: Lessons 6.1-6.4 (Complete React Router fundamentals)

📑 In This Lesson

🏛️ Understanding Layout Routes

Layout routes provide a way to share common UI elements (navigation, headers, footers, sidebars) across multiple pages while keeping the content unique for each route.

The Problem: Duplicated UI Code

Without layout routes, you'd repeat the same navigation and structure in every component:

// ❌ Without layouts - lots of duplication
function HomePage() {
  return (
    <>
      <Header />
      <Navigation />
      <main>
        <h1>Home Page</h1>
        {/* Home content */}
      </main>
      <Footer />
    </>
  );
}

function AboutPage() {
  return (
    <>
      <Header />      {/* Duplicated */}
      <Navigation />  {/* Duplicated */}
      <main>
        <h1>About Page</h1>
        {/* About content */}
      </main>
      <Footer />      {/* Duplicated */}
    </>
  );
}

The Solution: Layout Routes

Layout routes let you define shared UI once and reuse it across multiple pages:

// ✅ With layouts - no duplication
function MainLayout() {
  return (
    <>
      <Header />
      <Navigation />
      <main>
        <Outlet /> {/* Child routes render here */}
      </main>
      <Footer />
    </>
  );
}

function HomePage() {
  return <h1>Home Page</h1>;
}

function AboutPage() {
  return <h1>About Page</h1>;
}

📖 Layout Route Benefits

  • DRY Principle - Don't repeat shared UI code
  • Consistent UX - Navigation stays in place during transitions
  • Easier Maintenance - Update header once, affects all pages
  • Better Performance - Shared components don't remount
  • Flexible Architecture - Mix and match layouts as needed
  • Nested Layouts - Layouts can contain other layouts

How Layout Routes Work

flowchart TD A[User Navigates to /about] --> B[React Router] B --> C[Finds Layout Route] C --> D[Renders MainLayout] D --> E[MainLayout renders Header] D --> F[MainLayout renders Navigation] D --> G["MainLayout renders <Outlet />"] G --> H[Outlet renders About Page] D --> I[MainLayout renders Footer] style C fill:#667eea,color:#fff style G fill:#48bb78,color:#fff style H fill:#48bb78,color:#fff

Layout Route Anatomy

Component Purpose Example
Layout Component Contains shared UI structure MainLayout, AdminLayout
<Outlet /> Where child routes render Placeholder in layout
Parent Route Defines the layout route <Route element={<MainLayout />}>
Child Routes Pages that use the layout <Route path="about" element={<About />} />

Basic Layout Route Structure

import { Routes, Route, Outlet } from 'react-router-dom';

// Layout component
function MainLayout() {
  return (
    <div className="app-layout">
      <header>Header</header>
      <nav>Navigation</nav>
      <main>
        <Outlet /> {/* Child routes appear here */}
      </main>
      <footer>Footer</footer>
    </div>
  );
}

// Route configuration
function App() {
  return (
    <Routes>
      {/* Parent layout route */}
      <Route element={<MainLayout />}>
        {/* Child routes */}
        <Route path="/" element={<HomePage />} />
        <Route path="/about" element={<AboutPage />} />
        <Route path="/contact" element={<ContactPage />} />
      </Route>
    </Routes>
  );
}

💡 Key Concepts

  • Parent Route - The layout route has no path, only an element
  • Child Routes - Nested inside the parent, they define actual paths
  • Outlet - A placeholder that renders the active child route
  • Persistence - Layout doesn't unmount when switching between children

🔌 The Outlet Component

The <Outlet /> component is the magic that makes layout routes work. It acts as a placeholder where child routes will be rendered.

How Outlet Works

Think of Outlet like a slot machine - it shows whatever child route matches the current URL:

import { Outlet } from 'react-router-dom';

function Layout() {
  return (
    <div>
      <h1>My App</h1>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/about">About</Link>
      </nav>
      
      {/* The active child route renders here */}
      <Outlet />
      
      <footer>© 2024</footer>
    </div>
  );
}

// When URL is "/"      → Outlet renders HomePage
// When URL is "/about" → Outlet renders AboutPage

Outlet Context

You can pass data from the layout to child routes using Outlet context:

import { Outlet, useOutletContext } from 'react-router-dom';

// Layout passes context to children
interface LayoutContextType {
  user: { name: string; email: string };
  theme: 'light' | 'dark';
}

function Layout() {
  const [theme, setTheme] = useState<'light' | 'dark'>('light');
  
  const context: LayoutContextType = {
    user: { name: 'Alice', email: 'alice@example.com' },
    theme
  };
  
  return (
    <div className={`layout-${theme}`}>
      <header>
        <button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
          Toggle Theme
        </button>
      </header>
      
      {/* Pass context to child routes */}
      <Outlet context={context} />
    </div>
  );
}

// Child route accesses context
function ProfilePage() {
  const { user, theme } = useOutletContext<LayoutContextType>();
  
  return (
    <div>
      <h1>Profile for {user.name}</h1>
      <p>Email: {user.email}</p>
      <p>Theme: {theme}</p>
    </div>
  );
}

Multiple Outlets (Advanced)

You can have named outlets for complex layouts:

// Advanced: Named outlets (uncommon pattern)
function ComplexLayout() {
  return (
    <div className="complex-layout">
      <aside>
        <Outlet name="sidebar" />
      </aside>
      
      <main>
        <Outlet /> {/* Default outlet */}
      </main>
      
      <aside>
        <Outlet name="rightPanel" />
      </aside>
    </div>
  );
}

// Note: Named outlets are rarely needed
// Usually better to use separate layout components

⚠️ Common Outlet Mistakes

  • Forgetting Outlet - Layout needs <Outlet /> or children won't render
  • Multiple default Outlets - Only one unnamed <Outlet /> per layout
  • Outlet in wrong place - Should be where you want child content
  • Not wrapping Routes - Parent route must wrap child routes

Outlet vs Children

Feature <Outlet /> props.children
Use Case Route-based rendering Component composition
Content Source From nested routes From parent component
Dynamic Yes, based on URL No, passed explicitly
Context Can pass via context prop Via React Context or props

🎨 Creating Shared Layouts

Let's build practical, reusable layouts for real-world applications.

Basic Main Layout

A simple layout with header, navigation, and footer:

// layouts/MainLayout.tsx
import { Outlet, Link } from 'react-router-dom';
import './MainLayout.css';

export function MainLayout() {
  return (
    <div className="main-layout">
      {/* Header */}
      <header className="header">
        <div className="header-content">
          <Link to="/" className="logo">
            My App
          </Link>
          
          <nav className="header-nav">
            <Link to="/about">About</Link>
            <Link to="/contact">Contact</Link>
            <Link to="/login">Login</Link>
          </nav>
        </div>
      </header>
      
      {/* Main content area */}
      <main className="main-content">
        <Outlet />
      </main>
      
      {/* Footer */}
      <footer className="footer">
        <p>© 2024 My App. All rights reserved.</p>
      </footer>
    </div>
  );
}

Dashboard Layout with Sidebar

A layout with a persistent sidebar for dashboard pages:

// layouts/DashboardLayout.tsx
import { Outlet, NavLink } from 'react-router-dom';

export function DashboardLayout() {
  return (
    <div className="dashboard-layout">
      {/* Top bar */}
      <header className="dashboard-header">
        <h1>Dashboard</h1>
        <div className="user-menu">
          <span>Welcome, User</span>
          <button>Logout</button>
        </div>
      </header>
      
      <div className="dashboard-body">
        {/* Sidebar navigation */}
        <aside className="sidebar">
          <nav>
            <NavLink to="/dashboard" end>
              📊 Overview
            </NavLink>
            <NavLink to="/dashboard/analytics">
              📈 Analytics
            </NavLink>
            <NavLink to="/dashboard/reports">
              📄 Reports
            </NavLink>
            <NavLink to="/dashboard/settings">
              ⚙️ Settings
            </NavLink>
          </nav>
        </aside>
        
        {/* Content area */}
        <main className="dashboard-content">
          <Outlet />
        </main>
      </div>
    </div>
  );
}

Authentication Layout

A minimal layout for login/register pages:

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

export function AuthLayout() {
  return (
    <div className="auth-layout">
      <div className="auth-container">
        {/* Logo/branding */}
        <div className="auth-header">
          <Link to="/">
            <img src="/logo.svg" alt="Logo" />
          </Link>
          <h1>Welcome</h1>
        </div>
        
        {/* Auth form area */}
        <div className="auth-content">
          <Outlet />
        </div>
        
        {/* Footer links */}
        <div className="auth-footer">
          <Link to="/terms">Terms</Link>
          <Link to="/privacy">Privacy</Link>
          <Link to="/help">Help</Link>
        </div>
      </div>
    </div>
  );
}

Using Layouts in Routes

// App.tsx
import { Routes, Route } from 'react-router-dom';
import { MainLayout } from './layouts/MainLayout';
import { DashboardLayout } from './layouts/DashboardLayout';
import { AuthLayout } from './layouts/AuthLayout';

function App() {
  return (
    <Routes>
      {/* Main layout routes */}
      <Route element={<MainLayout />}>
        <Route path="/" element={<HomePage />} />
        <Route path="/about" element={<AboutPage />} />
        <Route path="/contact" element={<ContactPage />} />
      </Route>
      
      {/* Dashboard layout routes */}
      <Route element={<DashboardLayout />}>
        <Route path="/dashboard" element={<DashboardHome />} />
        <Route path="/dashboard/analytics" element={<Analytics />} />
        <Route path="/dashboard/reports" element={<Reports />} />
        <Route path="/dashboard/settings" element={<Settings />} />
      </Route>
      
      {/* Auth layout routes */}
      <Route element={<AuthLayout />}>
        <Route path="/login" element={<LoginPage />} />
        <Route path="/register" element={<RegisterPage />} />
        <Route path="/forgot-password" element={<ForgotPassword />} />
      </Route>
    </Routes>
  );
}

✅ Layout Design Best Practices

  • Semantic HTML - Use proper header, nav, main, aside, footer tags
  • Responsive design - Make layouts work on all screen sizes
  • Accessibility - Include ARIA labels, skip links, keyboard nav
  • Active states - Use NavLink to show current page
  • Loading states - Show progress during page transitions
  • Error boundaries - Wrap Outlet in error boundary

🏋️ Exercise: Create a Basic Layout

Build a simple application with a shared layout for multiple pages.

Requirements:

  1. Create a MainLayout component with header, navigation, and footer
  2. Use the Outlet component for child routes
  3. Create three pages: Home, About, Contact
  4. Configure routes so all pages use MainLayout
  5. Navigation should show active link styling
  6. Header should remain visible when switching pages
💡 Hint

Structure your routes like this:

<Routes>
  <Route element={<MainLayout />}>
    <Route path="/" element={<Home />} />
    <Route path="/about" element={<About />} />
    <Route path="/contact" element={<Contact />} />
  </Route>
</Routes>
✅ Solution
// layouts/MainLayout.tsx
import { Outlet, NavLink } from 'react-router-dom';

export function MainLayout() {
  return (
    <div className="app">
      <header>
        <h1>My Application</h1>
        <nav>
          <NavLink to="/" end>Home</NavLink>
          <NavLink to="/about">About</NavLink>
          <NavLink to="/contact">Contact</NavLink>
        </nav>
      </header>
      
      <main>
        <Outlet />
      </main>
      
      <footer>
        <p>© 2024 My Application</p>
      </footer>
    </div>
  );
}

// pages/Home.tsx
export function Home() {
  return <h2>Welcome to Home Page</h2>;
}

// pages/About.tsx
export function About() {
  return <h2>About Us</h2>;
}

// pages/Contact.tsx
export function Contact() {
  return <h2>Contact Us</h2>;
}

// App.tsx
import { Routes, Route } from 'react-router-dom';
import { MainLayout } from './layouts/MainLayout';
import { Home } from './pages/Home';
import { About } from './pages/About';
import { Contact } from './pages/Contact';

function App() {
  return (
    <Routes>
      <Route element={<MainLayout />}>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
      </Route>
    </Routes>
  );
}

🪆 Nested Route Structures

Nested routes allow you to create layouts within layouts, perfect for complex applications with multiple levels of navigation.

flowchart TD A[App Root] --> B[MainLayout] B --> C[Home Page] B --> D[Dashboard Layout] D --> E[Dashboard Home] D --> F[Settings Layout] F --> G[General Settings] F --> H[Privacy Settings] F --> I[Security Settings] style A fill:#667eea,color:#fff style B fill:#764ba2,color:#fff style D fill:#48bb78,color:#fff style F fill:#48bb78,color:#fff

Understanding Nested Routes

Each level of nesting adds another layout layer:

// Three levels of nesting
<Routes>
  {/* Level 1: Main app layout */}
  <Route element={<MainLayout />}>
    
    {/* Level 2: Dashboard layout */}
    <Route path="dashboard" element={<DashboardLayout />}>
      
      {/* Level 3: Settings layout */}
      <Route path="settings" element={<SettingsLayout />}>
        <Route path="general" element={<GeneralSettings />} />
        <Route path="privacy" element={<PrivacySettings />} />
      </Route>
      
    </Route>
  </Route>
</Routes>

// URL: /dashboard/settings/general
// Renders: MainLayout → DashboardLayout → SettingsLayout → GeneralSettings

Building a Nested Dashboard

Let's create a three-level nested structure:

// layouts/MainLayout.tsx
import { Outlet, NavLink } from 'react-router-dom';

export function MainLayout() {
  return (
    <div className="app">
      <header className="app-header">
        <h1>My App</h1>
        <nav>
          <NavLink to="/">Home</NavLink>
          <NavLink to="/dashboard">Dashboard</NavLink>
          <NavLink to="/about">About</NavLink>
        </nav>
      </header>
      
      <main>
        <Outlet /> {/* Level 1 content */}
      </main>
    </div>
  );
}

// layouts/DashboardLayout.tsx
export function DashboardLayout() {
  return (
    <div className="dashboard">
      <aside className="dashboard-sidebar">
        <h2>Dashboard</h2>
        <nav>
          <NavLink to="/dashboard" end>Overview</NavLink>
          <NavLink to="/dashboard/analytics">Analytics</NavLink>
          <NavLink to="/dashboard/users">Users</NavLink>
          <NavLink to="/dashboard/settings">Settings</NavLink>
        </nav>
      </aside>
      
      <div className="dashboard-main">
        <Outlet /> {/* Level 2 content */}
      </div>
    </div>
  );
}

// layouts/SettingsLayout.tsx
export function SettingsLayout() {
  return (
    <div className="settings">
      <div className="settings-nav">
        <h3>Settings</h3>
        <nav>
          <NavLink to="/dashboard/settings/general">General</NavLink>
          <NavLink to="/dashboard/settings/profile">Profile</NavLink>
          <NavLink to="/dashboard/settings/security">Security</NavLink>
          <NavLink to="/dashboard/settings/notifications">Notifications</NavLink>
        </nav>
      </div>
      
      <div className="settings-content">
        <Outlet /> {/* Level 3 content */}
      </div>
    </div>
  );
}

Configuring Nested Routes

// App.tsx
function App() {
  return (
    <Routes>
      {/* Level 1: Main Layout */}
      <Route element={<MainLayout />}>
        <Route path="/" element={<HomePage />} />
        <Route path="/about" element={<AboutPage />} />
        
        {/* Level 2: Dashboard Layout */}
        <Route path="/dashboard" element={<DashboardLayout />}>
          <Route index element={<DashboardHome />} />
          <Route path="analytics" element={<Analytics />} />
          <Route path="users" element={<Users />} />
          
          {/* Level 3: Settings Layout */}
          <Route path="settings" element={<SettingsLayout />}>
            <Route index element={<Navigate to="general" replace />} />
            <Route path="general" element={<GeneralSettings />} />
            <Route path="profile" element={<ProfileSettings />} />
            <Route path="security" element={<SecuritySettings />} />
            <Route path="notifications" element={<NotificationSettings />} />
          </Route>
        </Route>
      </Route>
    </Routes>
  );
}

Index Routes

Index routes render when the parent route matches exactly:

// Without index route:
// /dashboard          → Shows DashboardLayout but nothing in Outlet
// /dashboard/overview → Shows DashboardLayout with Overview in Outlet

// With index route:
<Route path="/dashboard" element={<DashboardLayout />}>
  <Route index element={<DashboardHome />} /> {/* Renders at /dashboard */}
  <Route path="overview" element={<Overview />} /> {/* Renders at /dashboard/overview */}
</Route>

// Now:
// /dashboard          → Shows DashboardLayout with DashboardHome
// /dashboard/overview → Shows DashboardLayout with Overview

💡 Index Route Tips

  • Use for default content - Show something when parent route matches
  • No path attribute - Index routes use the parent's path
  • Common pattern - Dashboard index shows overview
  • Alternative to redirect - Better UX than Navigate

Relative Routing in Nested Routes

// Inside a nested component at /dashboard/settings/profile

import { Link, useNavigate } from 'react-router-dom';

function ProfileSettings() {
  const navigate = useNavigate();
  
  // Absolute paths
  <Link to="/dashboard">Dashboard Home</Link>
  <Link to="/dashboard/settings/security">Security</Link>
  
  // Relative paths (relative to current route)
  <Link to="..">Back to Settings</Link>          {/* → /dashboard/settings */}
  <Link to="../general">General Settings</Link> {/* → /dashboard/settings/general */}
  <Link to="../../analytics">Analytics</Link>  {/* → /dashboard/analytics */}
  
  // Programmatic navigation
  navigate('..'); // Go up one level
  navigate('../security'); // Sibling route
  
  return <div>Profile Settings</div>;
}

Path Patterns in Nested Routes

Pattern Description Example
path="overview" Relative to parent /dashboard/overview
path="/overview" Absolute path /overview
index No path, parent path /dashboard (with index route)
path="*" Catch-all Any unmatched route

✅ Nested Route Best Practices

  • Keep nesting shallow - 2-3 levels max for maintainability
  • Use index routes - Provide default content for parent routes
  • Relative paths - Use relative navigation within sections
  • Consistent structure - Mirror URL structure in file organization
  • Breadcrumbs - Help users understand location in hierarchy

🎭 Multi-Layout Applications

Real applications often need different layouts for different sections. Let's build a complete multi-layout application architecture.

Planning Your Layouts

Layout Use Case Features
Public Layout Marketing pages Full navigation, footer, hero sections
Auth Layout Login/Register Minimal, centered form, no navigation
Dashboard Layout User workspace Sidebar, top bar, user menu
Admin Layout Admin panel Advanced sidebar, system status
Print Layout Printable pages Print-friendly, no nav/footer

Complete Multi-Layout Application

// layouts/PublicLayout.tsx
export function PublicLayout() {
  return (
    <div className="public-layout">
      <header className="public-header">
        <Link to="/" className="logo">My App</Link>
        <nav>
          <NavLink to="/features">Features</NavLink>
          <NavLink to="/pricing">Pricing</NavLink>
          <NavLink to="/about">About</NavLink>
        </nav>
        <div className="auth-links">
          <Link to="/login">Login</Link>
          <Link to="/register" className="btn-primary">Sign Up</Link>
        </div>
      </header>
      
      <main className="public-content">
        <Outlet />
      </main>
      
      <footer className="public-footer">
        <div className="footer-content">
          <div className="footer-section">
            <h4>Product</h4>
            <Link to="/features">Features</Link>
            <Link to="/pricing">Pricing</Link>
          </div>
          <div className="footer-section">
            <h4>Company</h4>
            <Link to="/about">About</Link>
            <Link to="/contact">Contact</Link>
          </div>
        </div>
        <p>© 2024 My App</p>
      </footer>
    </div>
  );
}

// layouts/AuthLayout.tsx
export function AuthLayout() {
  return (
    <div className="auth-layout">
      <div className="auth-box">
        <div className="auth-header">
          <Link to="/">
            <img src="/logo.svg" alt="Logo" />
          </Link>
        </div>
        
        <Outlet />
        
        <div className="auth-footer">
          <Link to="/help">Need help?</Link>
        </div>
      </div>
    </div>
  );
}

// layouts/AppLayout.tsx
export function AppLayout() {
  const { user, logout } = useAuth();
  
  return (
    <div className="app-layout">
      <header className="app-header">
        <Link to="/app" className="logo">My App</Link>
        
        <nav className="main-nav">
          <NavLink to="/app/dashboard">Dashboard</NavLink>
          <NavLink to="/app/projects">Projects</NavLink>
          <NavLink to="/app/team">Team</NavLink>
        </nav>
        
        <div className="user-menu">
          <span>{user?.name}</span>
          <button onClick={logout}>Logout</button>
        </div>
      </header>
      
      <main className="app-content">
        <Outlet />
      </main>
    </div>
  );
}

// layouts/AdminLayout.tsx
export function AdminLayout() {
  return (
    <div className="admin-layout">
      <aside className="admin-sidebar">
        <div className="sidebar-header">
          <h2>Admin Panel</h2>
        </div>
        
        <nav className="admin-nav">
          <NavLink to="/admin" end>
            📊 Dashboard
          </NavLink>
          <NavLink to="/admin/users">
            👥 Users
          </NavLink>
          <NavLink to="/admin/content">
            📝 Content
          </NavLink>
          <NavLink to="/admin/settings">
            ⚙️ Settings
          </NavLink>
          <NavLink to="/admin/analytics">
            📈 Analytics
          </NavLink>
        </nav>
        
        <div className="sidebar-footer">
          <Link to="/app">← Back to App</Link>
        </div>
      </aside>
      
      <div className="admin-main">
        <header className="admin-header">
          <h1>{/* Page title from child */}</h1>
          <div className="admin-actions">
            {/* Action buttons */}
          </div>
        </header>
        
        <main className="admin-content">
          <Outlet />
        </main>
      </div>
    </div>
  );
}

Route Configuration for Multiple Layouts

// App.tsx
import { Routes, Route, Navigate } from 'react-router-dom';
import { PublicLayout } from './layouts/PublicLayout';
import { AuthLayout } from './layouts/AuthLayout';
import { AppLayout } from './layouts/AppLayout';
import { AdminLayout } from './layouts/AdminLayout';
import { ProtectedRoute } from './components/ProtectedRoute';
import { RoleProtectedRoute } from './components/RoleProtectedRoute';

function App() {
  return (
    <Routes>
      {/* Public routes with PublicLayout */}
      <Route element={<PublicLayout />}>
        <Route path="/" element={<HomePage />} />
        <Route path="/features" element={<FeaturesPage />} />
        <Route path="/pricing" element={<PricingPage />} />
        <Route path="/about" element={<AboutPage />} />
        <Route path="/contact" element={<ContactPage />} />
      </Route>
      
      {/* Auth routes with AuthLayout */}
      <Route element={<AuthLayout />}>
        <Route path="/login" element={<LoginPage />} />
        <Route path="/register" element={<RegisterPage />} />
        <Route path="/forgot-password" element={<ForgotPasswordPage />} />
        <Route path="/reset-password" element={<ResetPasswordPage />} />
      </Route>
      
      {/* App routes with AppLayout (protected) */}
      <Route
        element={
          <ProtectedRoute>
            <AppLayout />
          </ProtectedRoute>
        }
      >
        <Route path="/app" element={<Navigate to="/app/dashboard" replace />} />
        <Route path="/app/dashboard" element={<Dashboard />} />
        <Route path="/app/projects" element={<Projects />} />
        <Route path="/app/projects/:projectId" element={<ProjectDetail />} />
        <Route path="/app/team" element={<Team />} />
        <Route path="/app/settings" element={<Settings />} />
      </Route>
      
      {/* Admin routes with AdminLayout (role-protected) */}
      <Route
        element={
          <RoleProtectedRoute allowedRoles={['admin']}>
            <AdminLayout />
          </RoleProtectedRoute>
        }
      >
        <Route path="/admin" element={<AdminDashboard />} />
        <Route path="/admin/users" element={<UserManagement />} />
        <Route path="/admin/content" element={<ContentManagement />} />
        <Route path="/admin/settings" element={<SystemSettings />} />
        <Route path="/admin/analytics" element={<AnalyticsDashboard />} />
      </Route>
      
      {/* 404 - No layout */}
      <Route path="*" element={<NotFoundPage />} />
    </Routes>
  );
}

✅ Multi-Layout Best Practices

  • Clear separation - Each layout serves a distinct purpose
  • Consistent patterns - Similar layouts use similar components
  • Shared components - Reuse nav items, headers across layouts
  • Layout switching - Smooth transitions between layouts
  • Protection layers - Wrap layouts with auth/role guards
  • Mobile responsive - All layouts work on mobile devices

Layout Composition Pattern

Create reusable layout building blocks:

// components/layouts/PageContainer.tsx
export function PageContainer({ 
  children, 
  maxWidth = 'lg' 
}: { 
  children: React.ReactNode;
  maxWidth?: 'sm' | 'md' | 'lg' | 'xl';
}) {
  return (
    <div className={`page-container max-w-${maxWidth}`}>
      {children}
    </div>
  );
}

// components/layouts/PageHeader.tsx
export function PageHeader({
  title,
  actions,
  breadcrumbs
}: {
  title: string;
  actions?: React.ReactNode;
  breadcrumbs?: React.ReactNode;
}) {
  return (
    <header className="page-header">
      {breadcrumbs}
      <div className="page-header-content">
        <h1>{title}</h1>
        {actions && <div className="page-actions">{actions}</div>}
      </div>
    </header>
  );
}

// Using composed layout components
function ProjectsPage() {
  return (
    <PageContainer>
      <PageHeader
        title="Projects"
        actions={
          <button>New Project</button>
        }
      />
      <div className="projects-grid">
        {/* Projects */}
      </div>
    </PageContainer>
  );
}

🍞 Dynamic Breadcrumbs

Breadcrumbs help users understand their location in the application hierarchy and provide quick navigation to parent pages.

flowchart LR A[Home] --> B[Dashboard] B --> C[Projects] C --> D[Project: ABC] D --> E[Settings] style A fill:#667eea,color:#fff style E fill:#48bb78,color:#fff

Basic Breadcrumb Component

// components/Breadcrumbs.tsx
import { Link, useLocation } from 'react-router-dom';

export function Breadcrumbs() {
  const location = useLocation();
  
  // Split path into segments
  const pathnames = location.pathname.split('/').filter(x => x);
  
  return (
    <nav aria-label="Breadcrumb" className="breadcrumbs">
      <ol>
        {/* Home link */}
        <li>
          <Link to="/">Home</Link>
        </li>
        
        {/* Dynamic segments */}
        {pathnames.map((segment, index) => {
          const path = `/${pathnames.slice(0, index + 1).join('/')}`;
          const isLast = index === pathnames.length - 1;
          
          // Format segment name
          const label = segment
            .replace(/-/g, ' ')
            .replace(/\b\w/g, char => char.toUpperCase());
          
          return (
            <li key={path}>
              <span className="separator">/</span>
              {isLast ? (
                <span aria-current="page">{label}</span>
              ) : (
                <Link to={path}>{label}</Link>
              )}
            </li>
          );
        })}
      </ol>
    </nav>
  );
}

// Usage in layout
function AppLayout() {
  return (
    <div>
      <header>Navigation</header>
      <Breadcrumbs />
      <main>
        <Outlet />
      </main>
    </div>
  );
}

Custom Breadcrumb Labels

Map route segments to custom labels:

// config/breadcrumbLabels.ts
export const breadcrumbLabels: Record<string, string> = {
  '/': 'Home',
  '/dashboard': 'Dashboard',
  '/dashboard/analytics': 'Analytics',
  '/dashboard/settings': 'Settings',
  '/projects': 'Projects',
  '/team': 'Team',
  '/admin': 'Administration',
  '/admin/users': 'User Management'
};

// components/SmartBreadcrumbs.tsx
import { Link, useLocation, useParams } from 'react-router-dom';
import { breadcrumbLabels } from '../config/breadcrumbLabels';

export function SmartBreadcrumbs() {
  const location = useLocation();
  const params = useParams();
  
  const pathnames = location.pathname.split('/').filter(x => x);
  
  const getBreadcrumbLabel = (path: string, segment: string): string => {
    // Check if we have a custom label
    if (breadcrumbLabels[path]) {
      return breadcrumbLabels[path];
    }
    
    // Check if segment is a parameter (like :projectId)
    // Try to replace with actual value
    const paramKey = Object.keys(params).find(key => 
      params[key] === segment
    );
    
    if (paramKey) {
      return `${paramKey}: ${segment}`;
    }
    
    // Default: format the segment
    return segment
      .replace(/-/g, ' ')
      .replace(/\b\w/g, char => char.toUpperCase());
  };
  
  return (
    <nav aria-label="Breadcrumb" className="breadcrumbs">
      <ol>
        <li>
          <Link to="/">🏠 Home</Link>
        </li>
        
        {pathnames.map((segment, index) => {
          const path = `/${pathnames.slice(0, index + 1).join('/')}`;
          const isLast = index === pathnames.length - 1;
          const label = getBreadcrumbLabel(path, segment);
          
          return (
            <li key={path}>
              <span className="separator"> › </span>
              {isLast ? (
                <span className="current">{label}</span>
              ) : (
                <Link to={path}>{label}</Link>
              )}
            </li>
          );
        })}
      </ol>
    </nav>
  );
}

Breadcrumbs with Data Fetching

Fetch actual names for dynamic routes:

// components/DataBreadcrumbs.tsx
import { Link, useLocation, useParams } from 'react-router-dom';
import { useState, useEffect } from 'react';

interface BreadcrumbItem {
  path: string;
  label: string;
  isLast: boolean;
}

export function DataBreadcrumbs() {
  const location = useLocation();
  const params = useParams();
  const [breadcrumbs, setBreadcrumbs] = useState<BreadcrumbItem[]>([]);
  
  useEffect(() => {
    const buildBreadcrumbs = async () => {
      const pathnames = location.pathname.split('/').filter(x => x);
      const items: BreadcrumbItem[] = [];
      
      for (let i = 0; i < pathnames.length; i++) {
        const path = `/${pathnames.slice(0, i + 1).join('/')}`;
        const segment = pathnames[i];
        let label = segment;
        
        // If this is a project ID, fetch the project name
        if (params.projectId === segment) {
          try {
            const response = await fetch(`/api/projects/${segment}`);
            const project = await response.json();
            label = project.name;
          } catch (err) {
            label = `Project ${segment}`;
          }
        }
        // If this is a user ID, fetch the user name
        else if (params.userId === segment) {
          try {
            const response = await fetch(`/api/users/${segment}`);
            const user = await response.json();
            label = user.name;
          } catch (err) {
            label = `User ${segment}`;
          }
        }
        // Otherwise format the segment
        else {
          label = segment
            .replace(/-/g, ' ')
            .replace(/\b\w/g, char => char.toUpperCase());
        }
        
        items.push({
          path,
          label,
          isLast: i === pathnames.length - 1
        });
      }
      
      setBreadcrumbs(items);
    };
    
    buildBreadcrumbs();
  }, [location.pathname, params]);
  
  return (
    <nav aria-label="Breadcrumb" className="breadcrumbs">
      <ol>
        <li>
          <Link to="/">Home</Link>
        </li>
        
        {breadcrumbs.map(crumb => (
          <li key={crumb.path}>
            <span className="separator"> / </span>
            {crumb.isLast ? (
              <span>{crumb.label}</span>
            ) : (
              <Link to={crumb.path}>{crumb.label}</Link>
            )}
          </li>
        ))}
      </ol>
    </nav>
  );
}

Breadcrumb Styling

/* Breadcrumb styles */
.breadcrumbs {
  padding: 1rem 0;
  font-size: 0.875rem;
}

.breadcrumbs ol {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  list-style: none;
  padding: 0;
  margin: 0;
}

.breadcrumbs li {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.breadcrumbs a {
  color: #667eea;
  text-decoration: none;
  transition: color 0.2s;
}

.breadcrumbs a:hover {
  color: #764ba2;
  text-decoration: underline;
}

.breadcrumbs .current {
  color: #333;
  font-weight: 500;
}

.breadcrumbs .separator {
  color: #999;
  user-select: none;
}

✅ Breadcrumb Best Practices

  • Skip trivial paths - Don't show every intermediate segment
  • Use semantic HTML - nav with aria-label="Breadcrumb"
  • Show current page - Use aria-current="page" on last item
  • Mobile friendly - Consider truncating on small screens
  • Consistent separators - Use › or / consistently
  • Fetch when needed - Get actual names for IDs

🎯 Advanced Layout Patterns

Explore sophisticated layout patterns for complex applications.

Conditional Layouts Based on Route

Automatically select layouts based on route patterns:

// hooks/useLayoutForRoute.ts
import { useLocation } from 'react-router-dom';

export function useLayoutForRoute() {
  const location = useLocation();
  
  if (location.pathname.startsWith('/admin')) {
    return 'admin';
  }
  
  if (location.pathname.startsWith('/app')) {
    return 'app';
  }
  
  if (['/login', '/register'].includes(location.pathname)) {
    return 'auth';
  }
  
  return 'public';
}

// App.tsx with conditional layout rendering
function App() {
  const layout = useLayoutForRoute();
  
  return (
    <div className={`layout-${layout}`}>
      <Routes>
        {/* Routes configuration */}
      </Routes>
    </div>
  );
}

Persistent Sidebar State

Remember sidebar collapsed state across navigation:

// layouts/DashboardLayoutWithState.tsx
import { useState, useEffect } from 'react';
import { Outlet } from 'react-router-dom';

export function DashboardLayoutWithState() {
  const [sidebarCollapsed, setSidebarCollapsed] = useState(() => {
    // Load from localStorage
    return localStorage.getItem('sidebarCollapsed') === 'true';
  });
  
  // Save to localStorage when changed
  useEffect(() => {
    localStorage.setItem('sidebarCollapsed', String(sidebarCollapsed));
  }, [sidebarCollapsed]);
  
  return (
    <div className={`dashboard ${sidebarCollapsed ? 'collapsed' : ''}`}>
      <aside className="sidebar">
        <button
          onClick={() => setSidebarCollapsed(!sidebarCollapsed)}
          aria-label={sidebarCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
        >
          {sidebarCollapsed ? '→' : '←'}
        </button>
        
        {!sidebarCollapsed && (
          <nav>
            {/* Navigation items */}
          </nav>
        )}
      </aside>
      
      <main className="content">
        <Outlet />
      </main>
    </div>
  );
}

Layout with Loading State

Show loading indicators during route transitions:

// layouts/LayoutWithLoading.tsx
import { Outlet, useNavigation } from 'react-router-dom';

export function LayoutWithLoading() {
  const navigation = useNavigation();
  const isLoading = navigation.state === 'loading';
  
  return (
    <div className="layout">
      <header>
        {/* Navigation */}
        {isLoading && (
          <div className="loading-bar">
            <div className="loading-progress"></div>
          </div>
        )}
      </header>
      
      <main className={isLoading ? 'loading' : ''}>
        <Outlet />
      </main>
    </div>
  );
}

Split Panel Layout

Resizable panels for complex UIs:

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

export function SplitPanelLayout() {
  const [leftWidth, setLeftWidth] = useState(300);
  const [isDragging, setIsDragging] = useState(false);
  
  const handleMouseDown = () => setIsDragging(true);
  
  const handleMouseUp = () => setIsDragging(false);
  
  const handleMouseMove = (e: React.MouseEvent) => {
    if (isDragging) {
      setLeftWidth(e.clientX);
    }
  };
  
  return (
    <div 
      className="split-panel-layout"
      onMouseMove={handleMouseMove}
      onMouseUp={handleMouseUp}
    >
      <div 
        className="left-panel"
        style={{ width: `${leftWidth}px` }}
      >
        {/* Left panel content */}
        <nav>Navigation</nav>
      </div>
      
      <div 
        className="resize-handle"
        onMouseDown={handleMouseDown}
      />
      
      <div className="right-panel">
        <Outlet />
      </div>
    </div>
  );
}

Modal Routes

Show modals on top of the current page:

// App.tsx with modal routes
function App() {
  const location = useLocation();
  const background = location.state?.background;
  
  return (
    <>
      <Routes location={background || location}>
        <Route element={<MainLayout />}>
          <Route path="/" element={<HomePage />} />
          <Route path="/projects" element={<ProjectsList />} />
          <Route path="/projects/:id" element={<ProjectDetail />} />
        </Route>
      </Routes>
      
      {/* Modal routes */}
      {background && (
        <Routes>
          <Route path="/projects/:id/edit" element={<EditProjectModal />} />
          <Route path="/projects/new" element={<NewProjectModal />} />
        </Routes>
      )}
    </>
  );
}

// Link that opens modal
<Link 
  to="/projects/123/edit"
  state={{ background: location }}
>
  Edit Project
</Link>

Layout Context Provider

Share layout state with all child components:

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

interface LayoutContextType {
  pageTitle: string;
  setPageTitle: (title: string) => void;
  pageActions: ReactNode;
  setPageActions: (actions: ReactNode) => void;
}

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

export function LayoutProvider({ children }: { children: ReactNode }) {
  const [pageTitle, setPageTitle] = useState('');
  const [pageActions, setPageActions] = useState<ReactNode>(null);
  
  return (
    <LayoutContext.Provider 
      value={{ pageTitle, setPageTitle, pageActions, setPageActions }}
    >
      {children}
    </LayoutContext.Provider>
  );
}

export function useLayout() {
  const context = useContext(LayoutContext);
  if (!context) {
    throw new Error('useLayout must be used within LayoutProvider');
  }
  return context;
}

// Layout uses context
function AppLayout() {
  const { pageTitle, pageActions } = useLayout();
  
  return (
    <div>
      <header>
        <h1>{pageTitle}</h1>
        {pageActions}
      </header>
      <main>
        <Outlet />
      </main>
    </div>
  );
}

// Pages set their title and actions
function ProjectsPage() {
  const { setPageTitle, setPageActions } = useLayout();
  
  useEffect(() => {
    setPageTitle('Projects');
    setPageActions(
      <button>New Project</button>
    );
    
    return () => {
      setPageTitle('');
      setPageActions(null);
    };
  }, [setPageTitle, setPageActions]);
  
  return <div>Projects list</div>;
}

💡 Advanced Pattern Tips

  • Context for layout state - Share state between layout and pages
  • Persistent UI state - Save preferences to localStorage
  • Loading indicators - Show progress during transitions
  • Modal routes - Keep background visible
  • Responsive layouts - Adapt to screen size
  • Theme support - Allow layout customization

🏋️ Exercise: Build Multi-Layout App

Create a complete application with multiple layouts and nested routes.

Requirements:

  1. Public layout for home and marketing pages
  2. Auth layout for login/register
  3. Dashboard layout with sidebar navigation
  4. Nested settings section with its own navigation
  5. Dynamic breadcrumbs showing current location
  6. Protected routes for dashboard
  7. All layouts should be mobile responsive
💡 Hint

Structure your routes in layers:

PublicLayout
  ├── Home
  ├── About
  └── Features

AuthLayout
  ├── Login
  └── Register

DashboardLayout (protected)
  ├── Overview
  ├── Projects
  └── SettingsLayout (nested)
      ├── General
      ├── Profile
      └── Security

✅ Best Practices

Follow these guidelines to create maintainable and effective layout structures in your React applications.

✅ Do's

  • Keep layouts simple and focused - Each layout should have a single responsibility. Don't try to handle too many different page types in one layout component.
  • Use semantic HTML - Wrap layouts in proper semantic tags like <header>, <nav>, <main>, <aside>, and <footer> for better accessibility and SEO.
  • Implement proper loading states - Show loading indicators during route transitions to provide feedback to users, especially for data-heavy pages.
  • Make layouts responsive - Design layouts that work well on all screen sizes. Use mobile-first CSS and consider collapsible sidebars for smaller screens.
  • Share layout state with Context - Use React Context to share layout-related state (like page title, breadcrumbs, actions) between the layout and child routes.
  • Persist user preferences - Save layout preferences like sidebar collapse state, theme selection, or panel sizes to localStorage for a better user experience.
  • Use meaningful breadcrumbs - Fetch actual names for dynamic route segments instead of showing IDs. Make breadcrumbs clickable for easy navigation.
  • Test layouts in isolation - Create stories or test harnesses for layouts to ensure they work correctly with different content and states.
  • Document layout expectations - Clearly document what props, context, or global state each layout expects from child routes.
  • Optimize for accessibility - Include skip links, proper ARIA labels, keyboard navigation support, and focus management in your layouts.

❌ Don'ts

  • Don't create monolithic layouts - Avoid putting all possible navigation, sidebars, headers, and footers into a single layout component. Split into specialized layouts instead.
  • Don't mix layout and business logic - Keep data fetching, business rules, and complex state management out of layout components. Layouts should focus on structure and UI.
  • Don't hardcode navigation items - Use configuration objects or derive navigation from your routes structure to keep things maintainable and DRY.
  • Don't ignore mobile users - Layouts that work well on desktop but break on mobile devices create a poor user experience. Always test on various screen sizes.
  • Don't forget error boundaries - Wrap layout content in error boundaries to prevent layout crashes from breaking the entire application.
  • Don't overuse nested layouts - Too many levels of nested layouts make the component tree complex and hard to debug. Keep nesting to 2-3 levels maximum.
  • Don't skip loading states - Sudden content changes without loading indicators confuse users. Always show feedback during transitions.
  • Don't block navigation - Avoid layout logic that prevents or delays route transitions. Keep layouts lightweight and non-blocking.
  • Don't duplicate layout code - If multiple layouts share common elements (like a header), extract them into reusable components.
  • Don't forget to clean up - If layouts set up event listeners, timers, or subscriptions, make sure to clean them up in useEffect cleanup functions.

💡 Pro Tips

  • Use layout-specific error pages - Create 404 and error pages that match each layout style instead of using a single global error page.
  • Implement layout transitions - Add smooth animations when switching between layouts to make the experience feel more polished.
  • Create layout preview modes - Build a dev tool that lets you preview all layouts side-by-side during development.
  • Use CSS Grid for complex layouts - CSS Grid is perfect for creating sophisticated layout structures with named template areas.
  • Lazy load layout components - Use React.lazy() to code-split large layout components that aren't needed on initial load.
  • Version your layouts - When making breaking changes, create new layout versions (v1, v2) and gradually migrate routes instead of breaking everything at once.
  • Build layout composition utilities - Create helper functions that make it easy to combine layout components in different ways.
  • Implement layout analytics - Track which layouts users see, how they navigate between them, and where they spend time to inform design decisions.
  • Use layout variants for A/B testing - Create layout variants to test different UI approaches and measure which performs better.
  • Document layout breakpoints - Clearly document at which screen sizes your layouts change behavior so designers and developers stay aligned.

🎯 Layout Architecture Checklist

Before deploying your layout system, verify:

  • ✅ Layouts are responsive and mobile-friendly
  • ✅ Navigation is consistent and intuitive
  • ✅ Loading states are implemented everywhere
  • ✅ Breadcrumbs work correctly for all routes
  • ✅ Authentication state is properly handled
  • ✅ Error boundaries protect against crashes
  • ✅ Accessibility features are in place
  • ✅ Layout preferences persist across sessions
  • ✅ Performance is optimized (lazy loading, code splitting)
  • ✅ Documentation exists for all layouts

📚 Summary

🎉 Key Takeaways

  • Layout routes provide structure - Use the Outlet component from React Router to create wrapper layouts that persist across child routes, reducing duplication and improving consistency.
  • Nested layouts enable hierarchical UI - You can nest layouts inside other layouts to create sophisticated application structures with multiple levels of navigation and context.
  • Index routes define default content - Use index routes to specify what should render when a parent route is matched exactly without any child routes.
  • Breadcrumbs improve navigation - Implement dynamic breadcrumbs that show users where they are in the application hierarchy and provide quick navigation to parent pages.
  • Context shares layout state - Use React Context to share state like page titles, actions, and preferences between layouts and their child routes.
  • Different layouts for different sections - Create specialized layouts for different parts of your application (public pages, authenticated areas, admin panels) to match their unique needs.
  • Loading states enhance UX - Show loading indicators during route transitions using React Router's useNavigation hook to provide feedback to users.
  • Accessibility matters in layouts - Include skip links, proper semantic HTML, ARIA labels, and keyboard navigation support to make layouts accessible to all users.
  • Persistent UI state improves experience - Save user preferences like sidebar collapse state and theme selection to localStorage so they persist across sessions.
  • Layout composition enables reusability - Break layouts into smaller, composable components like PageContainer and PageHeader that can be mixed and matched as needed.

🎯 What You Can Build Now

With the knowledge from this lesson, you're equipped to create sophisticated multi-layout applications:

graph TD A[Layout Routes] --> B[Multi-Section Apps] A --> C[Dashboard Applications] A --> D[Admin Panels] A --> E[Marketing Sites] B --> F[Public Layout] B --> G[Auth Layout] B --> H[App Layout] C --> I[Sidebar Navigation] C --> J[Nested Settings] C --> K[Dynamic Breadcrumbs] D --> L[Role-Based UI] D --> M[Data Tables] D --> N[Management Views] style A fill:#667eea,color:#fff style B fill:#48bb78,color:#fff style C fill:#48bb78,color:#fff style D fill:#48bb78,color:#fff style E fill:#48bb78,color:#fff

📚 Additional Resources

🔄 Practice Project Ideas

Reinforce your learning with these practice projects:

  1. Multi-tenant SaaS Application - Build an app with public marketing pages, authentication flow, and a dashboard with nested settings. Include role-based layouts for users and admins.
  2. E-commerce Platform - Create separate layouts for the storefront (with cart sidebar), checkout flow (minimal layout), and seller dashboard (with inventory management).
  3. Project Management Tool - Build an app with nested project/task views, a collapsible sidebar for navigation, dynamic breadcrumbs, and persistent layout preferences.
  4. Documentation Site - Create a documentation site with a sidebar TOC, nested category pages, breadcrumb navigation, and responsive layout that works great on mobile.
  5. Social Media Dashboard - Build a dashboard with multiple layouts: feed view, profile view, settings view, and admin moderation view with different navigation patterns.

🚀 What's Next?

In the next lesson, we'll put everything together in Module Project 6: Building a Complete Multi-Page Application. You'll create a fully-featured application that demonstrates all the routing concepts we've covered:

  • Multiple layouts for different sections
  • Nested routing with breadcrumb navigation
  • Protected routes with authentication
  • Dynamic routes with data loading
  • Search functionality with query parameters
  • Programmatic navigation and redirects
  • Error handling and 404 pages
  • Loading states and suspense boundaries

This project will bring together everything you've learned in Module 6 and create a production-ready application structure you can use as a foundation for your own projects!

💡 Layout Design Philosophy

"A great layout is invisible. It should provide structure and consistency without getting in the way of the content. Users should be able to focus on their tasks while the layout quietly handles navigation, context, and visual hierarchy."

As you build your applications, always ask: Does this layout help or hinder the user? Keep layouts simple, intuitive, and focused on enabling your users to accomplish their goals efficiently.

🎉 Congratulations!

You've mastered layout routes and nested routing patterns! You now have the skills to create sophisticated, multi-layout applications with professional navigation structures.

Ready to build something amazing? Let's move on to the Module 6 Project!