Layouts & Templates
Understanding Layouts in Next.js
Layouts are one of the most powerful features of the Next.js App Router. They allow you to create reusable UI structures that wrap around your pages, maintaining state and avoiding unnecessary re-renders when navigating between pages. This is crucial for building efficient, maintainable applications with consistent user interfaces.
Layouts solve common problems in web development:
- Avoiding duplicate code for headers, footers, and navigation across pages
- Preserving component state when navigating (e.g., scroll position, form inputs)
- Creating nested layout hierarchies for complex applications
- Optimizing performance by preventing unnecessary re-renders
Root Layout - The Foundation
Every Next.js App Router application must have a root layout. This is the top-level layout that wraps your entire application and is required in the app directory.
Creating the Root Layout
// app/layout.js
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
{/* Header appears on all pages */}
<header>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/blog">Blog</a>
</nav>
</header>
{/* Page content renders here */}
<main>{children}</main>
{/* Footer appears on all pages */}
<footer>
<p>© 2024 My Website</p>
</footer>
</body>
</html>
)
}
Root Layout Requirements
- Must be defined in
app/layout.jsorapp/layout.tsx - Must accept a
childrenprop - Must return
<html>and<body>tags - Cannot be a Client Component (no 'use client' directive)
Nested Layouts
One of the most powerful features of the App Router is the ability to create nested layouts. Each route segment can define its own layout that wraps the content of that segment and all its children.
Creating Nested Layouts
app/
├── layout.js # Root layout (wraps everything)
├── page.js # Home page
├── blog/
│ ├── layout.js # Blog layout (wraps all blog pages)
│ ├── page.js # Blog index (/blog)
│ └── [slug]/
│ └── page.js # Blog post (/blog/:slug)
└── dashboard/
├── layout.js # Dashboard layout (wraps all dashboard pages)
├── page.js # Dashboard home (/dashboard)
└── settings/
└── page.js # Dashboard settings (/dashboard/settings)
Example: Blog Layout
// app/blog/layout.js
export default function BlogLayout({ children }) {
return (
<div className="blog-container">
{/* Sidebar appears on all blog pages */}
<aside className="blog-sidebar">
<h3>Categories</h3>
<ul>
<li>Technology</li>
<li>Design</li>
<li>Business</li>
</ul>
</aside>
{/* Blog content renders here */}
<div className="blog-content">
{children}
</div>
</div>
)
}
Now when users navigate between /blog and /blog/my-post, the sidebar doesn't re-render—only the children content changes. This preserves state and improves performance.
Layout Hierarchy Example
// With this structure:
app/
├── layout.js # Root: <html><body><Header/>{children}<Footer/></body></html>
└── dashboard/
├── layout.js # Dashboard: <div><Sidebar/>{children}</div>
└── settings/
└── page.js # Settings page content
// The final rendered structure for /dashboard/settings is:
<html>
<body>
<Header /> {/* From root layout */}
<div>
<Sidebar /> {/* From dashboard layout */}
<SettingsPage /> {/* The actual page */}
</div>
<Footer /> {/* From root layout */}
</body>
</html>
Templates vs Layouts
While layouts preserve state across navigation, templates create a new instance on each navigation. This is useful when you need components to re-mount and re-initialize.
When to Use Templates
- When you want CSS/JavaScript animations to play on every navigation
- When you need to reset form state on navigation
- When you want to track page views on every route change
- When you need
useEffectto run on every navigation
Creating a Template
// app/blog/template.js
'use client'
import { useEffect } from 'react'
export default function BlogTemplate({ children }) {
// This effect runs on EVERY navigation within /blog
useEffect(() => {
console.log('Page changed - tracking view')
// Track page view in analytics
}, [])
return (
<div className="animate-fade-in">
{children}
</div>
)
}
Layout vs Template Comparison
Feature | Layout | Template -----------------------|-----------------|------------------ State preservation | Yes | No (remounts) Re-renders on nav | No | Yes (always) useEffect runs | Once | Every navigation Animations | First time only | Every time Performance | Better | Slightly slower Use case | Most scenarios | Special needs
Metadata Management
Layouts and pages can export metadata to control the HTML <head> content like title, description, and meta tags. This is crucial for SEO.
Static Metadata
// app/layout.js
export const metadata = {
title: 'My Website',
description: 'Welcome to my amazing website',
keywords: ['nextjs', 'react', 'web development'],
}
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
Dynamic Metadata
// app/blog/[slug]/page.js
export async function generateMetadata({ params }) {
// Fetch post data
const post = await fetch(`https://api.example.com/posts/${params.slug}`)
.then(res => res.json())
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.coverImage],
},
}
}
export default async function BlogPost({ params }) {
const post = await fetch(`https://api.example.com/posts/${params.slug}`)
.then(res => res.json())
return <article>{post.content}</article>
}
Metadata Priority
Metadata merges from root to leaf, with closer segments taking priority:
// app/layout.js
export const metadata = {
title: 'My Website',
description: 'Default description',
}
// app/blog/layout.js
export const metadata = {
title: 'Blog - My Website',
// description inherited from root
}
// app/blog/[slug]/page.js
export async function generateMetadata({ params }) {
return {
title: `${post.title} - Blog - My Website`,
description: post.excerpt, // Overrides parent description
}
}
// Final metadata for /blog/my-post:
// title: "My Post Title - Blog - My Website"
// description: "My post excerpt" (from page)
Metadata Template
You can define a template to avoid repeating the site name in every title:
// app/layout.js
export const metadata = {
title: {
template: '%s | My Website',
default: 'My Website', // Used when no title provided
},
description: 'Welcome to my website',
}
// app/blog/page.js
export const metadata = {
title: 'Blog',
// Final title: "Blog | My Website"
}
// app/blog/[slug]/page.js
export async function generateMetadata({ params }) {
const post = await fetchPost(params.slug)
return {
title: post.title,
// Final title: "Post Title | My Website"
}
}
Head Management with generateMetadata
The generateMetadata function provides advanced control over the <head> section:
// app/blog/[slug]/page.js
export async function generateMetadata({ params, searchParams }) {
const post = await fetchPost(params.slug)
return {
// Basic metadata
title: post.title,
description: post.excerpt,
keywords: post.tags,
// Open Graph (Facebook, LinkedIn)
openGraph: {
title: post.title,
description: post.excerpt,
type: 'article',
url: `https://mywebsite.com/blog/${params.slug}`,
images: [
{
url: post.coverImage,
width: 1200,
height: 630,
alt: post.title,
},
],
publishedTime: post.publishedAt,
authors: [post.author],
},
// Twitter Card
twitter: {
card: 'summary_large_image',
title: post.title,
description: post.excerpt,
images: [post.coverImage],
creator: '@myhandle',
},
// Additional meta tags
robots: {
index: true,
follow: true,
},
// Alternate languages
alternates: {
canonical: `https://mywebsite.com/blog/${params.slug}`,
languages: {
'en-US': `/en/blog/${params.slug}`,
'es-ES': `/es/blog/${params.slug}`,
},
},
}
}
Layout Components Best Practices
1. Keep Layouts Server Components
Layouts should be Server Components whenever possible to reduce JavaScript bundle size:
// ✅ Good - Server Component (default)
export default function Layout({ children }) {
return (
<div>
<StaticHeader />
{children}
</div>
)
}
// ❌ Avoid unless necessary
'use client'
export default function Layout({ children }) {
return (
<div>
<InteractiveHeader /> {/* Only if header needs interactivity */}
{children}
</div>
)
}
2. Compose with Client Components
When you need interactivity, create separate Client Components and import them into your Server Component layout:
// app/components/ThemeToggle.js
'use client'
import { useState } from 'react'
export default function ThemeToggle() {
const [theme, setTheme] = useState('light')
return <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Toggle Theme
</button>
}
// app/layout.js - Server Component
import ThemeToggle from './components/ThemeToggle'
export default function RootLayout({ children }) {
return (
<html>
<body>
<header>
<nav>...</nav>
<ThemeToggle /> {/* Client Component */}
</header>
{children}
</body>
</html>
)
}
3. Avoid Prop Drilling with Context
For shared state across layouts, use React Context in a Client Component:
// app/providers/ThemeProvider.js
'use client'
import { createContext, useState } from 'react'
export const ThemeContext = createContext()
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light')
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
)
}
// app/layout.js
import { ThemeProvider } from './providers/ThemeProvider'
export default function RootLayout({ children }) {
return (
<html>
<body>
<ThemeProvider>
{children}
</ThemeProvider>
</body>
</html>
)
}
Common Layout Patterns
1. Dashboard Layout with Sidebar
// app/dashboard/layout.js
import Sidebar from '@/components/Sidebar'
export default function DashboardLayout({ children }) {
return (
<div className="dashboard">
<Sidebar />
<main className="dashboard-content">
{children}
</main>
</div>
)
}
2. Multi-Column Layout
// app/blog/layout.js
export default function BlogLayout({ children }) {
return (
<div className="container">
<aside className="sidebar-left">
{/* Categories, tags, etc. */}
</aside>
<main className="content">
{children}
</main>
<aside className="sidebar-right">
{/* Ads, popular posts, etc. */}
</aside>
</div>
)
}
3. Conditional Layout
// app/dashboard/layout.js
import { headers } from 'next/headers'
export default function DashboardLayout({ children }) {
const headersList = headers()
const userAgent = headersList.get('user-agent')
const isMobile = /mobile/i.test(userAgent)
return (
<div className={isMobile ? 'mobile-layout' : 'desktop-layout'}>
{!isMobile && <Sidebar />}
<main>{children}</main>
</div>
)
}
- Create a root layout with a header, footer, and main content area
- Build a nested dashboard layout with a sidebar that only appears in the /dashboard section
- Implement a blog layout with a sidebar that persists across all blog pages
- Create a template that plays a fade-in animation on every page navigation
- Set up metadata for a blog post page that includes Open Graph and Twitter Card tags
- Implement a metadata template so all pages include " | Your Site Name" in their titles
- Create a layout that conditionally renders different navigation based on user authentication status
- Build a multi-language layout that sets the HTML lang attribute based on the current locale
- Implement a Context Provider in the root layout for theme switching across the entire app
Summary
Layouts and templates are fundamental building blocks in Next.js App Router applications. Understanding how to effectively use them is crucial for building performant, maintainable applications:
- Layouts preserve state and avoid re-renders during navigation, perfect for persistent UI elements
- Templates re-mount on every navigation, useful for animations and tracking
- Nested layouts allow complex UI hierarchies with optimal performance
- Metadata management in layouts and pages controls SEO-critical head elements
- Server Components should be the default for layouts to minimize JavaScript
- Client Components can be composed within Server Component layouts when interactivity is needed
By mastering layouts and templates, you can create sophisticated application structures that are both performant and maintainable. In the next lesson, we'll explore data fetching strategies in Next.js, including server-side rendering, static generation, and client-side fetching.