Next.js Layout System — Nested Layouts Guide

Sanjeev SharmaSanjeev Sharma
5 min read

Advertisement

Next.js Layout System — Nested Layouts Guide

Layouts wrap pages with shared UI. Nested layouts enable sophisticated organization where different route sections have different persistent layouts.

Root Layout

Every app needs a root layout:

// app/layout.tsx
import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'My App',
  description: 'Welcome to my app'
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        <header>Navigation here</header>
        <main>{children}</main>
        <footer>Footer content</footer>
      </body>
    </html>
  )
}

This wraps every page in your app.

Nested Layouts

Create layouts at any level to wrap route segments:

app/
├── layout.tsxRoot layout
├── page.tsx/
├── blog/
│   ├── layout.tsx/blog/* layout
│   ├── page.tsx            → /blog
│   └── [slug]/
│       └── page.tsx        → /blog/[slug]
├── admin/
│   ├── layout.tsx          → /admin/* layout
│   └── page.tsx            → /admin
// app/blog/layout.tsx
export default function BlogLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <div className="grid grid-cols-4 gap-4">
      <aside className="col-span-1">
        <nav>
          <a href="/blog">All Posts</a>
          <a href="/blog/category/react">React</a>
          <a href="/blog/category/nextjs">Next.js</a>
        </nav>
      </aside>
      <main className="col-span-3">{children}</main>
    </div>
  )
}

Layout Inheritance

Child layouts inherit parent layout structure:

Root (header + footer) ─┐
                        ├── Blog Layout (sidebar) ─┐
                        │                          ├── Post Page
                        │                          ├── Category Page
                        ├── Admin Layout (auth) ─┐
                        │                        ├── Dashboard
                        │                        ├── Settings

Multiple Layouts

Create different layouts for different sections:

// app/marketing/layout.tsx (Marketing site layout)
export default function MarketingLayout({ children }) {
  return (
    <div>
      <header className="bg-white border-b">
        <nav>Marketing Nav</nav>
      </header>
      <main>{children}</main>
      <footer>Marketing Footer</footer>
    </div>
  )
}
// app/app/layout.tsx (Application layout)
export default function AppLayout({ children }) {
  return (
    <div className="flex">
      <aside className="w-64 bg-gray-900 text-white">
        <nav>App Nav</nav>
      </aside>
      <div className="flex-1 flex flex-col">
        <header className="border-b">App Header</header>
        <main className="flex-1">{children}</main>
      </div>
    </div>
  )
}
app/
├── layout.tsxRoot
├── page.tsxLanding page
├── marketing/
│   ├── layout.tsxMarketing layout
│   ├── page.tsx/marketing
│   └── about/
│       └── page.tsx/marketing/about
├── app/
│   ├── layout.tsxApp layout
│   ├── page.tsx/app (dashboard)
│   └── settings/
│       └── page.tsx/app/settings

Route Groups for Layout Organization

Use route groups to organize layouts without affecting URLs:

app/
├── layout.tsxRoot
├── (marketing)/
│   ├── layout.tsxMarketing layout
│   ├── page.tsx/ (root)
│   ├── about/
│   │   └── page.tsx/about
│   └── blog/
│       └── page.tsx/blog
├── (admin)/
│   ├── layout.tsxAdmin layout
│   ├── page.tsx/ (but different layout!)
│   └── users/
│       └── page.tsx/users
// app/(marketing)/layout.tsx
export default function MarketingLayout({ children }) {
  return (
    <div>
      <nav>Marketing Nav</nav>
      {children}
    </div>
  )
}
// app/(admin)/layout.tsx
export default function AdminLayout({ children }) {
  return (
    <div className="flex">
      <sidebar>Admin Sidebar</sidebar>
      <main>{children}</main>
    </div>
  )
}

Now / and /about have marketing layout, while /admin and /admin/users have admin layout.

Dynamic Layouts

Create layouts that change based on data:

// app/user/[id]/layout.tsx
async function getUser(id: string) {
  return db.users.findUnique({ where: { id } })
}

export default async function UserLayout({
  children,
  params,
}: {
  children: React.ReactNode
  params: { id: string }
}) {
  const user = await getUser(params.id)

  return (
    <div className="grid gap-4">
      <header className="bg-blue-600 text-white p-4">
        <h1>{user.name}'s Profile</h1>
      </header>
      <div className="grid grid-cols-4 gap-4">
        <aside className="col-span-1">
          <img src={user.avatar} alt={user.name} className="rounded" />
          <p>{user.bio}</p>
        </aside>
        <main className="col-span-3">{children}</main>
      </div>
    </div>
  )
}

Metadata in Layouts

Define metadata at layout level:

// app/blog/layout.tsx
import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'Blog',
  description: 'Read my latest blog posts',
}

export default function BlogLayout({ children }) {
  return <div>{children}</div>
}

Child pages can override this metadata.

Real-World: E-Commerce Layout

// app/layout.tsx
export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <header>
          <Logo />
          <SearchBar />
          <CartIcon />
        </header>
        <main>{children}</main>
        <footer>
          <Links />
          <Newsletter />
        </footer>
      </body>
    </html>
  )
}
// app/shop/layout.tsx
export default function ShopLayout({ children }) {
  return (
    <div className="grid grid-cols-4 gap-4">
      <aside className="col-span-1">
        <CategoryFilter />
        <PriceFilter />
      </aside>
      <main className="col-span-3">{children}</main>
    </div>
  )
}
// app/shop/page.tsx
export default function ShopPage() {
  return <ProductGrid />
}
// app/shop/[id]/layout.tsx
export default function ProductLayout({ children }) {
  return (
    <div className="grid grid-cols-2 gap-4">
      <div>{children}</div>
      <aside>
        <RelatedProducts />
        <Reviews />
      </aside>
    </div>
  )
}

Layout Persistence

Layouts persist across page navigation, maintaining state:

// app/dashboard/layout.tsx
'use client'

import { useState } from 'react'

export default function DashboardLayout({ children }) {
  const [sidebarOpen, setSidebarOpen] = useState(true)

  return (
    <div className="flex">
      <aside className={sidebarOpen ? 'w-64' : 'w-16'}>
        <Sidebar open={sidebarOpen} />
      </aside>
      <main className="flex-1">
        <button onClick={() => setSidebarOpen(!sidebarOpen)}>
          Toggle Sidebar
        </button>
        {children}
      </main>
    </div>
  )
}

As users navigate /dashboard/analytics to /dashboard/users, the sidebar state persists.

FAQ

Q: Can I have different layouts for different pages in the same route? A: Yes, use route groups to create separate nested layouts for different sections.

Q: How do I prevent a layout from persisting? A: Layouts persist by design. Use Client Components with state management if you need to clear state on navigation.

Q: Can layouts fetch data? A: Yes, layouts can be async Server Components and fetch data.


Master layouts for clean, organized application structure.

Advertisement

Sanjeev Sharma

Written by

Sanjeev Sharma

Full Stack Engineer · E-mopro