| | |

React Router 09 ⚛️ Nested Routes — Layout routing with Outlet

⏱️ Estimated read: 10 min | |

The previous chapters covered the flat route configuration and the navigation links. This chapter covers the nested route structure: the route tree where a parent route renders a shared layout and the child routes render inside it via the <Outlet /> component. The LFCS exam does not test React Router, but front-end engineering certification paths do. The critical distinction is between the route path and the URL path. The nested route’s path is relative to the parent’s path, not the absolute URL. The dashboard child under the /app parent matches /app/dashboard, not /dashboard. The <Outlet /> is the placeholder. The parent’s component renders the layout (the sidebar, the header, the navigation), and the <Outlet /> is the slot where the matched child route renders. Without the <Outlet />, the child routes never appear, and the URL changes but the page does not.

Key point: A nested route is a route object with a children array. The parent route’s path is the prefix, and the child routes’ paths are relative to it. The parent’s component renders the layout, and the <Outlet /> inside it is the placeholder for the child. The index route ({ index: true }) is the default child that renders at the parent’s path. The pathless route (no path) is a layout-only route that does not add a URL segment.


Why nested routes require a specific mental model

The relative path problem. The nested route’s path is relative to the parent’s path. The /app parent with the dashboard child creates the /app/dashboard URL. The /app parent with the /dashboard child (leading slash) creates the /dashboard URL, which is outside the parent’s layout. The leading slash breaks the nesting. The correct form for a nested route has no leading slash.

The <Outlet /> problem. The parent’s component must render the <Outlet /> where the child route should appear. The layout is the parent’s component: the sidebar, the header, the footer. The <Outlet /> is the slot for the child. Without it, the child route matches but does not render. The URL changes, but the page content remains the parent’s. The <Outlet /> is the most forgotten piece.

The index route problem. The index route is the default child. The parent path is /app. The /app URL matches the parent, but no child matches. The index route ({ index: true, Component: DashboardHome }) provides the default content. Without the index route, the /app URL renders only the layout with an empty <Outlet />. The index route is the landing page for the parent’s path.

The pathless layout problem. A pathless route has no path property. It does not add a URL segment. The route renders the layout (the provider, the error boundary, the sidebar), and the <Outlet /> renders the children. The pathless route is the mechanism for the shared layout that does not correspond to a URL. The /app and /settings routes can share the pathless layout, which renders the sidebar without adding /layout to the URL.

The parameter inheritance problem. The parent route’s path parameters are available in the child route. The /users/:userId parent and the posts/:postId child match the /users/1/posts/42 URL. The child’s useParams returns both userId and postId. The parameters are merged from the entire matched route chain.

The trade-off. The nested routes are the natural structure for the application. The URL structure and the component structure mirror each other. The flat routes are simpler for the small applications but require duplicating the layout in every component. The nested routes are the correct choice for the applications with the shared navigation.


a. The basic nested route with the <Outlet />

The parent route renders the layout, and the <Outlet /> is the placeholder for the child.

import { createBrowserRouter, Outlet, Link } from "react-router";
import { RouterProvider } from "react-router/dom";

function DashboardLayout() {
  return (
    <div className="dashboard">
      <aside>
        <nav>
          <Link to="/dashboard">Home</Link>
          <Link to="/dashboard/settings">Settings</Link>
        </nav>
      </aside>
      <main>
        <Outlet />
      </main>
    </div>
  );
}

function DashboardHome() {
  return <h1>Dashboard Home</h1>;
}

function DashboardSettings() {
  return <h1>Settings</h1>;
}

const router = createBrowserRouter([
  {
    path: "/dashboard",
    Component: DashboardLayout,
    children: [
      { index: true, Component: DashboardHome },
      { path: "settings", Component: DashboardSettings },
    ],
  },
]);

The DashboardLayout renders the sidebar and the <Outlet />. The /dashboard URL matches the index route and renders the DashboardHome inside the <Outlet />. The /dashboard/settings URL matches the settings child and renders the DashboardSettings. The sidebar remains in both cases because the parent’s component is rendered around the <Outlet />.


b. The index route and the pathless layout

The index route is the default child. The parent path with no matching child renders the index route.

{
  path: "/dashboard",
  Component: DashboardLayout,
  children: [
    { index: true, Component: DashboardHome },
    { path: "settings", Component: DashboardSettings },
    { path: "analytics", Component: DashboardAnalytics },
  ],
}

The /dashboard URL matches the index route. The /dashboard/settings URL matches the settings route. The index route cannot have children. The index: true is the marker.

The pathless route is the layout that does not add a URL segment.

{
  Component: AppProviders,
  children: [
    {
      path: "/dashboard",
      Component: DashboardLayout,
      children: [
        { index: true, Component: DashboardHome },
        { path: "settings", Component: DashboardSettings },
      ],
    },
    {
      path: "/settings",
      Component: SettingsLayout,
      children: [
        { index: true, Component: SettingsHome },
        { path: "profile", Component: SettingsProfile },
      ],
    },
  ],
}

The AppProviders is a pathless route. It renders the providers (the theme, the auth, the query client) around the <Outlet />. The URL for the dashboard is /dashboard, not /app-providers/dashboard. The pathless route is the layout mechanism that does not affect the URL.


c. The nested parameter and the path inheritance

The nested route inherits the parent’s parameters. The useParams hook returns all parameters from the matched chain.

function UserLayout() {
  const { userId } = useParams();
  return (
    <div>
      <h1>User {userId}</h1>
      <Outlet />
    </div>
  );
}

function UserPost() {
  const { userId, postId } = useParams();
  return <p>Post {postId} by user {userId}</p>;
}

const router = createBrowserRouter([
  {
    path: "users/:userId",
    Component: UserLayout,
    children: [
      { path: "posts/:postId", Component: UserPost },
    ],
  },
]);

The /users/1/posts/42 URL matches the nested route. The UserLayout renders the user heading and the <Outlet />. The UserPost renders inside the <Outlet /> with the userId and postId from the useParams. The parameters are the union of the parent’s and the child’s params.

The relative navigation uses the to prop without the leading slash. The <Link to="posts/42"> inside the UserLayout navigates to /users/1/posts/42 if the current location is /users/1. The relative path is the mechanism for the navigation within the nested layout.

<Link to="posts/42">Post 42</Link>

The absolute path (/users/1/posts/42) works but is brittle. The relative path is the preferred form in the nested routes.


Complete Example Session

# ============================================
# PART 1: CREATE THE PARENT LAYOUT
# ============================================

function DashboardLayout() {
  return (
    <div>
      <nav><Link to="/dashboard">Home</Link></nav>
      <Outlet />
    </div>
  );
}
# ============================================
# PART 2: CREATE THE CHILD ROUTES
# ============================================

function DashboardHome() { return <h1>Home</h1>; }
function DashboardSettings() { return <h1>Settings</h1>; }
# ============================================
# PART 3: CREATE THE ROUTER
# ============================================

const router = createBrowserRouter([
  {
    path: "/dashboard",
    Component: DashboardLayout,
    children: [
      { index: true, Component: DashboardHome },
      { path: "settings", Component: DashboardSettings },
    ],
  },
]);
# ============================================
# PART 4: NAVIGATE
# ============================================

# /dashboard → DashboardHome inside layout
# /dashboard/settings → DashboardSettings inside layout
# ============================================
# PART 5: ADD PATHLESS LAYOUT
# ============================================

{
  Component: AppProviders,
  children: [
    { path: "/dashboard", Component: DashboardLayout, children: [...] },
  ],
}
# ============================================
# PART 6: ADD NESTED PARAMS
# ============================================

{
  path: "users/:userId",
  Component: UserLayout,
  children: [
    { path: "posts/:postId", Component: UserPost },
  ],
}
# ============================================
# PART 7: ACCESS PARAMS
# ============================================

function UserPost() {
  const { userId, postId } = useParams();
  return <p>Post {postId} by user {userId}</p>;
}
# ============================================
# PART 8: RELATIVE LINK
# ============================================

<Link to="posts/42">Post 42</Link>
// From /users/1 navigates to /users/1/posts/42
# ============================================
# PART 9: VERIFY
# ============================================

# /users/1 → UserLayout with index route
# /users/1/posts/42 → UserLayout + UserPost
# /dashboard → DashboardLayout + Home

The nine parts trace the nested route: parent layout, child routes, router config, navigation, pathless layout, nested params, param access, relative link, and verification.


Quick Reference

Route Object Properties

PropertyPurpose
pathURL pattern (relative when nested)
ComponentReact component
childrenNested routes
indexDefault child route

Nested Path Forms

FormMeaning
path: "settings"Relative to parent
path: "/settings"Absolute (breaks nesting)
index: trueDefault child

Layout Components

ComponentPurpose
<Outlet />Child route placeholder
<Link to="relative">Relative navigation
<NavLink>Active state link

Parameter Inheritance

LevelAvailable Params
ParentParent’s params
ChildParent + child params

Best Practices

✅ Do This:

<Outlet />
<!-- Render the Outlet in the parent layout -->       # ✅
{ index: true, Component: Home }
<!-- Add the default child route -->                 # ✅
<Link to="posts/42">Post</Link>
<!-- Use relative navigation in the layout -->       # ✅

❌ Don’t Do This:

# Omit the Outlet
<!-- Child routes never render -->                   # ❌
path: "/settings"
# Under a parent route
<!-- Breaks the nesting -->                         # ❌
# Forget the index route
<!-- /dashboard renders empty layout -->             # ❌

Common Pitfalls

PitfallWhy It HappensFix
Child routes not renderingMissing <Outlet />Add Outlet
Path mismatchLeading slash in childRemove the slash
Default child missingNo index: trueAdd index route
Parent params lostChild route outside parentNest the route
Relative link wrongNo leading slashRelative is correct

Real-World Examples

1. Basic Layout

function Layout() { return <div><nav /><Outlet /></div>; }

2. Index Route

{ index: true, Component: Home }

3. Nested Route

{ path: "settings", Component: Settings }

4. Pathless Layout

{ Component: Providers, children: [...] }

5. Parent Param

const { userId } = useParams();

6. Child Param

const { userId, postId } = useParams();

7. Relative Link

<Link to="posts/42">Post</Link>

8. Multiple Levels

{ path: "users", children: [{ path: ":id", children: [{ path: "posts" }] }] }

9. Outlet Context

<Outlet context={{ user }} />

10. useOutletContext

const { user } = useOutletContext();

Visual

Nested Route Structure

┌──────────────────────────────────────────────┐
│  /dashboard (layout)                         │
│  ├── <nav />                                 │
│  ├── <Outlet />                              │
│  │   ├── / (index) → Home                    │
│  │   └── /settings → Settings                │
└──────────────────────────────────────────────┘

URL to Component

┌──────────────────────────────────────────────┐
│  URL: /dashboard/settings                    │
└──────────────────┬───────────────────────────┘
                   │
                   ▼
┌──────────────────────────────────────────────┐
│  Match: DashboardLayout + Settings           │
└──────────────────┬───────────────────────────┘
                   │
                   ▼
┌──────────────────────────────────────────────┐
│  Render: Layout wraps Settings via Outlet    │
└──────────────────────────────────────────────┘

Summary

ItemValue
Nested routeschildren array
Placeholder<Outlet />
Default childindex: true
Pathless layoutNo path
Parent paramsAvailable in child
Relative linkto="path"
Path inheritanceRelative to parent

Key takeaways:

  • The nested route’s path is relative to the parent. The dashboard child under the /app parent matches /app/dashboard. The leading slash (/dashboard) breaks the nesting. The relative path is the default.
  • The <Outlet /> is the placeholder. The parent’s component renders the layout, and the <Outlet /> renders the child. Without the <Outlet />, the child route matches but does not render.
  • The index route is the default child. The { index: true } route renders at the parent’s path. Without it, the /app URL renders only the layout with an empty <Outlet />.
  • The pathless route is the layout without a URL segment. It renders the providers, the error boundary, or the sidebar around the <Outlet /> without adding a segment to the URL.
  • The parent parameters are available in the child. The useParams returns the union of the parent’s and the child’s params. The child route can use the parent’s params without re-declaring them.

Remember: The nested route is the layout mechanism. The children is the nesting. The <Outlet /> is the placeholder. The index is the default. The pathless route is the layout-only. The parent params are inherited. The exam tests the <Outlet /> and the relative path.

Was this helpful?


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!