Skip to main content
Chapter 2 App Router, Layouts, and Navigation

Nested Layouts and Shared UI

20 min read Lesson 6 / 28

Layouts: The App Router Superpower

Layouts wrap pages in persistent UI that does not re-render on navigation. This makes it trivial to have a shell that stays mounted while the content changes — a pattern that previously required complex setup in single-page applications.

Root Layout

Every App Router project requires a root layout:

// app/layout.tsx
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import './globals.css';

const inter = Inter({ subsets: ['latin'] });

export const metadata: Metadata = {
    title: {
        template: '%s | My App',
        default: 'My App',
    },
    description: 'Built with Next.js 15',
};

export default function RootLayout({
    children,
}: {
    children: React.ReactNode;
}) {
    return (
        <html lang="en">
            <body className={inter.className}>
                <header>
                    <nav>/* Global navigation */</nav>
                </header>
                <main>{children}</main>
                <footer>/* Global footer */</footer>
            </body>
        </html>
    );
}

Nested Layouts

Layouts compose. A dashboard layout adds a sidebar without affecting the root layout:

// app/(dashboard)/layout.tsx
import { Sidebar } from '@/components/sidebar';

export default function DashboardLayout({
    children,
}: {
    children: React.ReactNode;
}) {
    return (
        <div className="flex h-screen">
            <Sidebar className="w-64 shrink-0" />
            <div className="flex-1 overflow-y-auto p-8">
                {children}
            </div>
        </div>
    );
}

Metadata Per Layout

Each layout and page can export metadata that gets merged:

// app/(dashboard)/layout.tsx
import type { Metadata } from 'next';

export const metadata: Metadata = {
    title: {
        template: '%s | Dashboard',
        default: 'Dashboard',
    },
};
// app/(dashboard)/settings/page.tsx
import type { Metadata } from 'next';

export const metadata: Metadata = {
    title: 'Settings', // Renders as "Settings | Dashboard | My App"
};

Template vs Layout

Use template.tsx instead of layout.tsx when you want the wrapper to re-mount on each navigation (useful for page transition animations):

// app/blog/template.tsx — re-mounts on every blog navigation
export default function BlogTemplate({ children }: { children: React.ReactNode }) {
    return (
        <div className="animate-fade-in">
            {children}
        </div>
    );
}

Layouts are one of the most powerful features of the App Router — use them to eliminate repetitive wrapper components across your pages.