Layouts & Templates
Nested layouts, templates, parallel routes, and intercepting routes in Next.js
Root Layout
The root app/layout.tsx wraps every page. It replaces the HTML and body tags:
// app/layout.tsx
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html>
<body>
<header>Site Header</header>
{children}
</body>
</html>
)
}
Nested Layouts
Any route segment can define its own layout.tsx. Layouts persist across navigation — they don't re-render:
app/
├── dashboard/
│ ├── layout.tsx → sidebar persists
│ ├── page.tsx → /dashboard
│ └── settings/
│ └── page.tsx → /dashboard/settings
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className="flex">
<nav className="w-64">Dashboard Sidebar</nav>
<main>{children}</main>
</div>
)
}
Layout vs Template
Use layout when state should persist across pages (sidebar, nav). Use template for page-transition effects or when you need useEffect to run on every navigation.
Parallel Routes (slots)
Use @slot to render multiple pages in the same layout independently:
app/
└── dashboard/
├── layout.tsx
├── @feed/
│ └── page.tsx
└── @analytics/
└── page.tsx
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
feed,
analytics,
}: {
children: React.ReactNode
feed: React.ReactNode
analytics: React.ReactNode
}) {
return (
<div>
<aside>{feed}</aside>
<section>{children}</section>
<aside>{analytics}</aside>
</div>
)
}
Intercepting Routes
Use (..) to intercept routes from a parent segment — useful for modals:
app/
└── feed/
├── page.tsx → /feed
└── (..)photos/
└── [id]/
└── page.tsx → intercepts /photos/[id] from /feed
Route Groups
Organize routes without affecting URL paths:
app/
├── (marketing)/
│ ├── layout.tsx
│ ├── page.tsx → /
│ └── pricing/
│ └── page.tsx → /pricing
└── (app)/
├── layout.tsx
└── dashboard/
└── page.tsx → /dashboard
See It In Action
1// app/dashboard/layout.tsx — Persistent sidebar across dashboard sub-pages2"use client"3 4import Link from "next/link"5import { usePathname } from "next/navigation"6 7const sidebarLinks = [8{ href: "/dashboard", label: "Home" },9{ href: "/dashboard/settings", label: "Settings" },10{ href: "/dashboard/analytics", label: "Analytics" },11]12 13export default function DashboardLayout({14children,15}: {16children: React.ReactNode17}) {18const pathname = usePathname()19 20return (21 22<div className="flex min-h-screen">23<nav className="flex w-64 flex-col gap-2 border-r bg-gray-50 p-4 dark:bg-gray-900">24<h2 className="mb-4 text-lg font-semibold">Dashboard</h2>25{sidebarLinks.map((link) => {26const isActive = pathname === link.href27return (28<Link29key={link.href}30href={link.href}31className={`rounded-md px-3 py-2 text-sm transition-colors ${32 isActive33 ? "bg-blue-600 text-white"34 : "text-gray-700 hover:bg-gray-200 dark:text-gray-300 dark:hover:bg-gray-800"35 }`} >36{link.label}37</Link>38)39})}40</nav>41<main className="flex-1 p-6">{children}</main>42</div>43)44}This layout renders a persistent sidebar with navigation links using Next.js Link components and the usePathname hook for active-link highlighting. The sidebar stays mounted — and its state (active link, scroll position) survives — as the user navigates between /dashboard, /dashboard/settings, and /dashboard/analytics. Only the <main> content re-renders, demonstrating layout persistence.