React Router 09 ⚛️ Nested Routes — Layout routing with Outlet
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
| Property | Purpose |
|---|---|
path | URL pattern (relative when nested) |
Component | React component |
children | Nested routes |
index | Default child route |
Nested Path Forms
| Form | Meaning |
|---|---|
path: "settings" | Relative to parent |
path: "/settings" | Absolute (breaks nesting) |
index: true | Default child |
Layout Components
| Component | Purpose |
|---|---|
<Outlet /> | Child route placeholder |
<Link to="relative"> | Relative navigation |
<NavLink> | Active state link |
Parameter Inheritance
| Level | Available Params |
|---|---|
| Parent | Parent’s params |
| Child | Parent + 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
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Child routes not rendering | Missing <Outlet /> | Add Outlet |
| Path mismatch | Leading slash in child | Remove the slash |
| Default child missing | No index: true | Add index route |
| Parent params lost | Child route outside parent | Nest the route |
| Relative link wrong | No leading slash | Relative 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
| Item | Value |
|---|---|
| Nested routes | children array |
| Placeholder | <Outlet /> |
| Default child | index: true |
| Pathless layout | No path |
| Parent params | Available in child |
| Relative link | to="path" |
| Path inheritance | Relative to parent |
Key takeaways:
- The nested route’s path is relative to the parent. The
dashboardchild under the/appparent 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/appURL 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
useParamsreturns 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.
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!