Nested Routes and Layout Patterns

Creating Complex UI Structure with Nested Routes in React Router

Understanding Nested Routes

Nested routes are a powerful feature of React Router that allow you to define routes hierarchically, where a parent route serves as a layout or container for its child routes. This approach enables you to:

flowchart TD App[App Component] --> Nav[Navigation Bar] App --> Routes Routes --> Home[Home Route] Routes --> Dashboard[Dashboard Route] Dashboard --> DashSidebar[Dashboard Sidebar] Dashboard --> DashContent[Dashboard Content] DashContent --> Overview[Overview Route] DashContent --> Profile[Profile Route] DashContent --> Settings[Settings Route] classDef container fill:#f5f5f5,stroke:#333,stroke-width:1px; classDef layout fill:#ffe,stroke:#aa3,stroke-width:1px; classDef route fill:#dfd,stroke:#373,stroke-width:1px; class App,Dashboard,DashContent container; class Nav,DashSidebar layout; class Home,Overview,Profile,Settings route;

In this diagram, the Dashboard route acts as a container with its own layout (sidebar), and it renders different child routes (Overview, Profile, Settings) within its content area. The parent Dashboard component stays mounted while the child routes change.

Real-World Analogy: Office Building Layout

Nested routes are like the physical layout of an office building:

  • The building itself (parent route) has common elements like the entrance, elevators, and security desk
  • Different floors (child routes) share these common elements but contain different departments
  • Each floor might have its own reception area (layout) that remains consistent for all rooms on that floor
  • Moving between rooms on the same floor (navigating between sibling routes) doesn't require going back to the main entrance

Just as you wouldn't rebuild the entire building structure when moving between offices, nested routes allow you to keep common UI elements mounted while swapping out the specific content.

Setting Up Nested Routes

In React Router 6, nested routes can be defined either through component nesting in JSX or through path nesting in route configuration objects.

Using the Outlet Component

The Outlet component is the key to nested routes. It acts as a placeholder where child routes will be rendered within the parent component.


// Basic nested routes example
import { BrowserRouter, Routes, Route, Outlet, Link } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        
        {/* Parent route */}
        <Route path="/dashboard" element={<Dashboard />}>
          {/* Child routes */}
          <Route index element={<DashboardOverview />} />
          <Route path="profile" element={<Profile />} />
          <Route path="settings" element={<Settings />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

// Parent component with Outlet
function Dashboard() {
  return (
    <div className="dashboard-container">
      <div className="dashboard-sidebar">
        <h2>Dashboard</h2>
        <nav>
          <ul>
            <li><Link to="/dashboard">Overview</Link></li>
            <li><Link to="/dashboard/profile">Profile</Link></li>
            <li><Link to="/dashboard/settings">Settings</Link></li>
          </ul>
        </nav>
      </div>
      
      <div className="dashboard-content">
        {/* Child routes render here */}
        <Outlet />
      </div>
    </div>
  );
}

// Child components
function DashboardOverview() {
  return <h2>Dashboard Overview</h2>;
}

function Profile() {
  return <h2>User Profile</h2>;
}

function Settings() {
  return <h2>Settings</h2>;
}

function Home() {
  return (
    <div>
      <h1>Home Page</h1>
      <Link to="/dashboard">Go to Dashboard</Link>
    </div>
  );
}
        

In this example:

Relative Links in Nested Routes

When working with nested routes, you can use relative links to simplify navigation between related routes:


function Dashboard() {
  return (
    <div className="dashboard-container">
      <div className="dashboard-sidebar">
        <h2>Dashboard</h2>
        <nav>
          <ul>
            {/* Relative links - relative to current route */}
            <li><Link to=".">Overview</Link></li>
            <li><Link to="profile">Profile</Link></li>
            <li><Link to="settings">Settings</Link></li>
          </ul>
        </nav>
      </div>
      
      <div className="dashboard-content">
        <Outlet />
      </div>
    </div>
  );
}
        

Relative links work like this:

This makes your routing more maintainable because you don't need to update all your links if you change the parent route path.

Route Paths in Nested Routes

When using nested routes, it's important to understand how paths combine:

Parent Route Child Route Full Path
/dashboard profile /dashboard/profile
/users/:userId posts /users/:userId/posts
/app settings/notifications /app/settings/notifications

Child routes can also have their own nested routes, creating deeply nested structures.

Index Routes

Index routes allow you to render a default child route when the parent route path is matched exactly. They're useful for providing a default view or home page within a nested route structure.


<Route path="/dashboard" element={<Dashboard />}>
  {/* This renders at /dashboard */}
  <Route index element={<DashboardOverview />} />
  
  {/* These render at their respective paths */}
  <Route path="profile" element={<Profile />} />
  <Route path="settings" element={<Settings />} />
</Route>
        

Without an index route, navigating to /dashboard would display the Dashboard layout but with an empty <Outlet />. The index route fills this gap by providing content for the parent route's exact path.

Nested Index Routes

You can use index routes at multiple levels of nesting:


<Route path="/dashboard" element={<Dashboard />}>
  <Route index element={<DashboardOverview />} />
  
  <Route path="settings" element={<Settings />}>
    {/* This renders at /dashboard/settings */}
    <Route index element={<GeneralSettings />} />
    
    {/* These render at their respective paths */}
    <Route path="account" element={<AccountSettings />} />
    <Route path="notifications" element={<NotificationSettings />} />
  </Route>
</Route>
        

In this example, navigating to /dashboard/settings would render the GeneralSettings component within the Settings component, which itself renders within the Dashboard component.

Real-World Example: E-commerce Categories

A practical example of index routes is in an e-commerce category structure:


<Route path="/shop" element={<Shop />}>
  {/* Show featured products at /shop */}
  <Route index element={<FeaturedProducts />} />
  
  <Route path="clothing" element={<ClothingCategory />}>
    {/* Show all clothing at /shop/clothing */}
    <Route index element={<AllClothing />} />
    
    {/* Subcategories */}
    <Route path="mens" element={<MensClothing />} />
    <Route path="womens" element={<WomensClothing />} />
    <Route path="kids" element={<KidsClothing />} />
  </Route>
  
  <Route path="electronics" element={<ElectronicsCategory />}>
    {/* Show all electronics at /shop/electronics */}
    <Route index element={<AllElectronics />} />
    
    {/* Subcategories */}
    <Route path="phones" element={<Phones />} />
    <Route path="computers" element={<Computers />} />
    <Route path="accessories" element={<Accessories />} />
  </Route>
</Route>
          

This structure creates a natural hierarchy that matches how customers browse an online store:

  • Shop home shows featured products
  • Category pages show all items in that category
  • Subcategory pages show more specific items

Each level maintains consistent navigation and layout while changing only the products displayed.

Layout Patterns with Nested Routes

Nested routes enable powerful layout patterns that help structure your application UI in a consistent and maintainable way.

Basic Layout Pattern

The simplest layout pattern uses a parent route as a container with common elements, and child routes for the specific content:


function Layout() {
  return (
    <div className="app-container">
      <header>
        <h1>My App</h1>
        <nav>
          <Link to="/">Home</Link>
          <Link to="/about">About</Link>
          <Link to="/contact">Contact</Link>
        </nav>
      </header>
      
      <main>
        <Outlet />
      </main>
      
      <footer>
        <p>© 2025 My App</p>
      </footer>
    </div>
  );
}

// Using the layout
<Routes>
  <Route element={<Layout />}>
    <Route path="/" element={<Home />} />
    <Route path="/about" element={<About />} />
    <Route path="/contact" element={<Contact />} />
  </Route>
</Routes>
        

Note that the parent route doesn't have a path specified, which means it matches all routes and acts purely as a layout container.

Multiple Layout Patterns

You can define different layouts for different sections of your application:


function MainLayout() {
  return (
    <div>
      <MainHeader />
      <Outlet />
      <MainFooter />
    </div>
  );
}

function DashboardLayout() {
  return (
    <div className="dashboard-layout">
      <DashboardHeader />
      <div className="dashboard-container">
        <DashboardSidebar />
        <main>
          <Outlet />
        </main>
      </div>
    </div>
  );
}

function AdminLayout() {
  return (
    <div className="admin-layout">
      <AdminHeader />
      <div className="admin-container">
        <AdminSidebar />
        <main>
          <Outlet />
        </main>
      </div>
    </div>
  );
}

// Routes with different layouts
<Routes>
  {/* Public pages with main layout */}
  <Route element={<MainLayout />}>
    <Route path="/" element={<Home />} />
    <Route path="/about" element={<About />} />
    <Route path="/contact" element={<Contact />} />
  </Route>
  
  {/* User dashboard with dashboard layout */}
  <Route path="/dashboard" element={<DashboardLayout />}>
    <Route index element={<DashboardHome />} />
    <Route path="profile" element={<Profile />} />
    <Route path="settings" element={<Settings />} />
  </Route>
  
  {/* Admin section with admin layout */}
  <Route path="/admin" element={<AdminLayout />}>
    <Route index element={<AdminHome />} />
    <Route path="users" element={<UserManagement />} />
    <Route path="settings" element={<AdminSettings />} />
  </Route>
</Routes>
        

This approach creates a clear separation between different sections of your app, each with its own navigation and structure.

Real-World Example: Dashboard with Multiple Sections

Here's a more comprehensive example of a dashboard with multiple sections:


// DashboardLayout.js
function DashboardLayout() {
  return (
    <div className="dashboard">
      <header className="dashboard-header">
        <div className="logo">MyApp</div>
        <div className="search-bar">
          <input type="text" placeholder="Search..." />
        </div>
        <div className="user-menu">
          <UserMenu />
        </div>
      </header>
      
      <div className="dashboard-container">
        <nav className="dashboard-sidebar">
          <ul>
            <li>
              <NavLink to="/dashboard" end>
                <span className="icon">📊</span> Overview
              </NavLink>
            </li>
            <li>
              <NavLink to="/dashboard/analytics">
                <span className="icon">📈</span> Analytics
              </NavLink>
            </li>
            <li>
              <NavLink to="/dashboard/projects">
                <span className="icon">📁</span> Projects
              </NavLink>
            </li>
            <li>
              <NavLink to="/dashboard/messages">
                <span className="icon">✉️</span> Messages
              </NavLink>
            </li>
            <li>
              <NavLink to="/dashboard/settings">
                <span className="icon">⚙️</span> Settings
              </NavLink>
            </li>
          </ul>
        </nav>
        
        <main className="dashboard-content">
          <Outlet />
        </main>
      </div>
    </div>
  );
}

// Dashboard routing
<Route path="/dashboard" element={<DashboardLayout />}>
  <Route index element={<DashboardOverview />} />
  <Route path="analytics" element={<Analytics />} />
  
  {/* Projects section with its own nested routes */}
  <Route path="projects" element={<Projects />}>
    <Route index element={<ProjectsList />} />
    <Route path=":projectId" element={<ProjectDetails />} />
    <Route path="new" element={<NewProject />} />
  </Route>
  
  {/* Messages section with its own nested routes */}
  <Route path="messages" element={<Messages />}>
    <Route index element={<InboxList />} />
    <Route path=":messageId" element={<MessageDetail />} />
    <Route path="compose" element={<ComposeMessage />} />
  </Route>
  
  {/* Settings section with its own nested routes */}
  <Route path="settings" element={<Settings />}>
    <Route index element={<GeneralSettings />} />
    <Route path="profile" element={<ProfileSettings />} />
    <Route path="notifications" element={<NotificationSettings />} />
    <Route path="security" element={<SecuritySettings />} />
  </Route>
</Route>
          

This structured approach creates a consistent dashboard experience while allowing for deep navigation within each section.

Nested Layout Components

You can create even more specific layouts by nesting layout components. This is particularly useful for sections that have their own navigation or structure.


// Projects section with its own layout
function ProjectsLayout() {
  return (
    <div className="projects-container">
      <div className="projects-header">
        <h1>Projects</h1>
        <Link to="/dashboard/projects/new" className="button">
          New Project
        </Link>
      </div>
      
      <div className="projects-tabs">
        <NavLink to="/dashboard/projects" end>All Projects</NavLink>
        <NavLink to="/dashboard/projects/active">Active</NavLink>
        <NavLink to="/dashboard/projects/archived">Archived</NavLink>
      </div>
      
      <div className="projects-content">
        <Outlet />
      </div>
    </div>
  );
}

// Using the nested layout
<Route path="/dashboard" element={<DashboardLayout />}>
  <Route index element={<DashboardOverview />} />
  
  {/* Projects section with its own layout */}
  <Route path="projects" element={<ProjectsLayout />}>
    <Route index element={<AllProjects />} />
    <Route path="active" element={<ActiveProjects />} />
    <Route path="archived" element={<ArchivedProjects />} />
    <Route path=":projectId" element={<ProjectDetails />} />
    <Route path="new" element={<NewProject />} />
  </Route>
  
  {/* Other dashboard routes */}
</Route>
        

This creates a three-level hierarchy:

  1. DashboardLayout (header, sidebar, content area)
  2. ProjectsLayout (projects header, tabs, content area)
  3. Specific project views (all, active, archived, details, etc.)

Split Pane Layouts

Another common layout pattern is the split pane or master-detail view, often used for email clients, chat applications, or document management systems:


function MessagesLayout() {
  const { messageId } = useParams();
  
  return (
    <div className="messages-container">
      <div className="messages-sidebar">
        <div className="messages-actions">
          <Link to="/dashboard/messages/compose" className="button">
            Compose
          </Link>
        </div>
        
        <div className="messages-folders">
          <NavLink to="/dashboard/messages" end>Inbox</NavLink>
          <NavLink to="/dashboard/messages/sent">Sent</NavLink>
          <NavLink to="/dashboard/messages/drafts">Drafts</NavLink>
          <NavLink to="/dashboard/messages/trash">Trash</NavLink>
        </div>
        
        <MessageList selectedId={messageId} />
      </div>
      
      <div className="messages-content">
        <Outlet />
      </div>
    </div>
  );
}

// Using the split pane layout
<Route path="/dashboard/messages" element={<MessagesLayout />}>
  <Route index element={<NoMessageSelected />} />
  <Route path=":messageId" element={<MessageDetail />} />
  <Route path="compose" element={<ComposeMessage />} />
  <Route path="sent" element={<SentMessages />} />
  <Route path="drafts" element={<DraftMessages />} />
  <Route path="trash" element={<TrashMessages />} />
</Route>
        

Real-World Example: Multi-Panel Admin Interface

Administrative interfaces often have complex layout requirements. Here's how you might structure one:

flowchart TB AdminLayout --> AppHeader AdminLayout --> AppSidebar AdminLayout --> ContentArea ContentArea --> TableToolbar ContentArea --> DataTable ContentArea --> Pagination DataTable --> ActionMenu style AdminLayout fill:#f5f5f5,stroke:#333,stroke-width:2px style ContentArea fill:#e5e5ff,stroke:#333,stroke-width:1px style DataTable fill:#e5ffe5,stroke:#333,stroke-width:1px

// AdminLayout.js
function AdminLayout() {
  return (
    <div className="admin-layout">
      <header className="admin-header">
        <div className="logo">Admin Panel</div>
        <div className="admin-search">
          <input type="text" placeholder="Search..." />
        </div>
        <div className="admin-user-menu">
          <AdminUserMenu />
        </div>
      </header>
      
      <div className="admin-container">
        <nav className="admin-sidebar">
          <AdminSidebarMenu />
        </nav>
        
        <main className="admin-content">
          <Outlet />
        </main>
      </div>
    </div>
  );
}

// UsersManagement.js
function UsersManagement() {
  const [searchParams, setSearchParams] = useSearchParams();
  const page = parseInt(searchParams.get('page') || '1', 10);
  const perPage = parseInt(searchParams.get('perPage') || '10', 10);
  const sortBy = searchParams.get('sortBy') || 'name';
  const sortOrder = searchParams.get('sortOrder') || 'asc';
  
  return (
    <div className="users-management">
      <header className="content-header">
        <h1>Users Management</h1>
        <div className="actions">
          <Link to="/admin/users/new" className="button">
            Add User
          </Link>
          <button className="button">Export</button>
        </div>
      </header>
      
      <div className="filters">
        <div className="filter-group">
          <label>Role:</label>
          <select 
            value={searchParams.get('role') || ''}
            onChange={e => {
              const newParams = new URLSearchParams(searchParams);
              if (e.target.value) {
                newParams.set('role', e.target.value);
              } else {
                newParams.delete('role');
              }
              setSearchParams(newParams);
            }}
          >
            <option value="">All Roles</option>
            <option value="admin">Admin</option>
            <option value="editor">Editor</option>
            <option value="user">User</option>
          </select>
        </div>
        
        <div className="filter-group">
          {/* More filters */}
        </div>
      </div>
      
      <div className="content-body">
        <div className="data-table">
          <table>
            <thead>
              <tr>
                <th>Name</th>
                <th>Email</th>
                <th>Role</th>
                <th>Status</th>
                <th>Actions</th>
              </tr>
            </thead>
            <tbody>
              {/* Table rows here */}
            </tbody>
          </table>
        </div>
        
        <div className="pagination">
          <button 
            disabled={page === 1}
            onClick={() => {
              const newParams = new URLSearchParams(searchParams);
              newParams.set('page', (page - 1).toString());
              setSearchParams(newParams);
            }}
          >
            Previous
          </button>
          
          <span>Page {page}</span>
          
          <button 
            onClick={() => {
              const newParams = new URLSearchParams(searchParams);
              newParams.set('page', (page + 1).toString());
              setSearchParams(newParams);
            }}
          >
            Next
          </button>
        </div>
      </div>
    </div>
  );
}

// Admin routes configuration
<Route path="/admin" element={<AdminLayout />}>
  <Route index element={<AdminDashboard />} />
  
  {/* Users management */}
  <Route path="users" element={<Outlet />}>
    <Route index element={<UsersManagement />} />
    <Route path=":userId" element={<UserDetails />} />
    <Route path="new" element={<NewUser />} />
  </Route>
  
  {/* Products management */}
  <Route path="products" element={<Outlet />}>
    <Route index element={<ProductsManagement />} />
    <Route path=":productId" element={<ProductDetails />} />
    <Route path="new" element={<NewProduct />} />
    <Route path="categories" element={<ProductCategories />} />
  </Route>
  
  {/* Settings */}
  <Route path="settings" element={<AdminSettings />} />
</Route>
          

This example demonstrates multiple levels of UI hierarchy:

  • The admin layout with global header and sidebar
  • Content sections with their own headers, filters, and actions
  • Data tables with sorting, filtering, and pagination
  • Detail views for individual records

All of this is organized using nested routes, with each level adding its own UI structure.

Sharing Data Between Routes

Nested routes provide opportunities to share data and state between parent and child routes. This can be achieved in several ways:

Using Context

Create a context in the parent route and provide it to all child routes:


// Create a context for the dashboard
const DashboardContext = createContext();

function DashboardLayout() {
  const [sidebarOpen, setSidebarOpen] = useState(true);
  const [theme, setTheme] = useState('light');
  
  // Data and functions to share with child routes
  const dashboardValue = {
    sidebarOpen,
    toggleSidebar: () => setSidebarOpen(prev => !prev),
    theme,
    toggleTheme: () => setTheme(prev => prev === 'light' ? 'dark' : 'light')
  };
  
  return (
    <DashboardContext.Provider value={dashboardValue}>
      <div className={`dashboard ${theme}`}>
        <header className="dashboard-header">
          {/* Header content */}
          <button onClick={dashboardValue.toggleSidebar}>
            {sidebarOpen ? 'Hide' : 'Show'} Sidebar
          </button>
          <button onClick={dashboardValue.toggleTheme}>
            Toggle Theme
          </button>
        </header>
        
        <div className="dashboard-container">
          {sidebarOpen && (
            <nav className="dashboard-sidebar">
              {/* Sidebar content */}
            </nav>
          )}
          
          <main className="dashboard-content">
            <Outlet />
          </main>
        </div>
      </div>
    </DashboardContext.Provider>
  );
}

// Hook to use dashboard context in child routes
export function useDashboard() {
  const context = useContext(DashboardContext);
  if (!context) {
    throw new Error('useDashboard must be used within a DashboardLayout');
  }
  return context;
}

// Using the shared context in a child route
function DashboardOverview() {
  const { theme, toggleTheme } = useDashboard();
  
  return (
    <div>
      <h1>Dashboard Overview</h1>
      <p>Current theme: {theme}</p>
      <button onClick={toggleTheme}>Toggle Theme</button>
    </div>
  );
}
        

Using useOutletContext

React Router provides useOutletContext as a simpler alternative to creating your own context:


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

function DashboardLayout() {
  const [sidebarOpen, setSidebarOpen] = useState(true);
  const [theme, setTheme] = useState('light');
  
  // Data and functions to share with child routes
  const dashboardContext = {
    sidebarOpen,
    toggleSidebar: () => setSidebarOpen(prev => !prev),
    theme,
    toggleTheme: () => setTheme(prev => prev === 'light' ? 'dark' : 'light')
  };
  
  return (
    <div className={`dashboard ${theme}`}>
      {/* Dashboard layout */}
      
      {/* Pass context to Outlet */}
      <main className="dashboard-content">
        <Outlet context={dashboardContext} />
      </main>
    </div>
  );
}

// In a child route
function DashboardOverview() {
  // Access the context provided by the parent route
  const { theme, toggleTheme } = useOutletContext();
  
  return (
    <div>
      <h1>Dashboard Overview</h1>
      <p>Current theme: {theme}</p>
      <button onClick={toggleTheme}>Toggle Theme</button>
    </div>
  );
}
        

Using URL Parameters

Another way to share data between routes is through URL parameters:


// Routes with parameters
<Route path="/projects" element={<ProjectsLayout />}>
  <Route path=":projectId" element={<ProjectDetails />}>
    <Route index element={<ProjectOverview />} />
    <Route path="tasks" element={<ProjectTasks />} />
    <Route path="team" element={<ProjectTeam />} />
    <Route path="settings" element={<ProjectSettings />} />
  </Route>
</Route>

// Parent route accessing the parameter
function ProjectDetails() {
  const { projectId } = useParams();
  const [project, setProject] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    // Fetch project data
    const fetchProject = async () => {
      setLoading(true);
      try {
        const response = await fetch(`/api/projects/${projectId}`);
        const data = await response.json();
        setProject(data);
      } catch (error) {
        console.error('Error fetching project:', error);
      } finally {
        setLoading(false);
      }
    };
    
    fetchProject();
  }, [projectId]);
  
  if (loading) {
    return <div>Loading project...</div>;
  }
  
  if (!project) {
    return <div>Project not found</div>;
  }
  
  // Pass project data to child routes via context
  return (
    <div className="project-details">
      <header>
        <h1>{project.name}</h1>
        <div className="project-meta">
          <span>Status: {project.status}</span>
          <span>Due: {new Date(project.dueDate).toLocaleDateString()}</span>
        </div>
      </header>
      
      <nav className="project-tabs">
        <NavLink to={``} end>Overview</NavLink>
        <NavLink to={`tasks`}>Tasks</NavLink>
        <NavLink to={`team`}>Team</NavLink>
        <NavLink to={`settings`}>Settings</NavLink>
      </nav>
      
      <div className="project-content">
        <Outlet context={project} />
      </div>
    </div>
  );
}

// Child route using the project data
function ProjectTasks() {
  const project = useOutletContext();
  
  return (
    <div>
      <h2>Tasks for {project.name}</h2>
      <ul>
        {project.tasks.map(task => (
          <li key={task.id}>{task.title}</li>
        ))}
      </ul>
    </div>
  );
}
        

Data Flow in Nested Routes

Here's how data typically flows in a nested route structure:

flowchart TD URL[URL: /projects/123/tasks] URL --> ProjectsLayoutParam[ProjectsLayout reads nothing] ProjectsLayoutParam --> ProjectDetailsParam[ProjectDetails reads projectId: 123] ProjectDetailsParam --> ProjectTasksParam[ProjectTasks reads nothing] ProjectDetailsParam --> FetchProject[Fetch project data] FetchProject --> ProjectData[Project data] ProjectData --> OutletContext[Pass via Outlet context] OutletContext --> ProjectTasksData[ProjectTasks receives project data] classDef param fill:#ffd,stroke:#aa3,stroke-width:1px; classDef data fill:#dfd,stroke:#3a3,stroke-width:1px; classDef flow fill:#ddf,stroke:#33a,stroke-width:1px; class ProjectsLayoutParam,ProjectDetailsParam,ProjectTasksParam param; class ProjectData,ProjectTasksData data; class FetchProject,OutletContext flow;

This pattern is particularly powerful for:

  • Complex data-driven interfaces
  • Master-detail views
  • Wizards and multi-step forms
  • Tabbed interfaces for viewing different aspects of a resource

Practice Activities

Activity 1: Basic Nested Routes

Create a simple application with the following nested route structure:

  1. A main layout with header, sidebar, and content area
  2. Home, About, and Contact routes that render within the layout
  3. A dashboard route with its own nested routes:
    • Dashboard overview (index route)
    • Profile page
    • Settings page

Ensure that navigation works correctly at all levels.

Activity 2: Complex Layout Patterns

Extend the application to include more advanced layout patterns:

  1. Create multiple layouts for different sections (main, dashboard, admin)
  2. Implement a settings section with its own nested routes and tabs
  3. Create a master-detail view for a list of products
  4. Add an admin section with tables, filtering, and pagination

Focus on creating reusable layout components and consistent navigation.

Activity 3: Data Sharing and Context

Implement data sharing between nested routes:

  1. Create a context for the dashboard that provides theme settings and UI state
  2. Use useOutletContext to pass data from a parent route to its children
  3. Implement a project details page that fetches data and shares it with child routes
  4. Create a shopping cart that persists across different product pages

Ensure that data flows correctly between related routes and that UI state is consistent.

Summary

Further Resources