Real applications repeat UI everywhere: the same header on every page, the same sidebar across a dashboard. The App Router turns this repetition into a first-class concept with layouts. In this lesson you will learn how pages and layouts nest, why layouts preserve state during navigation, and when to reach for a template instead.
A page is the UI that is unique to a URL. It is always the default export of page.js:
// app/dashboard/page.js
export default function DashboardPage() {
return <h1>Dashboard Overview</h1>;
}Pages are Server Components by default, so they can fetch data directly - more on that in Lessons 6 and 7.
A layout wraps a page and every route below it. Add a layout.js to a folder and it receives the nested content as the children prop:
// app/dashboard/layout.js
import Sidebar from './Sidebar';
export default function DashboardLayout({ children }) {
return (
<div style={{ display: 'flex' }}>
<Sidebar />
<section>{children}</section>
</div>
);
}Now /dashboard, /dashboard/settings, and any other nested route all render inside this sidebar shell automatically.
Every app has exactly one required root layout at app/layout.js. It must render the html and body tags, and it replaces the old _document and _app files from the Pages Router era. Global CSS, fonts, and site-wide providers belong here.
Layouts compose from the outside in. For the URL /dashboard/settings, Next.js renders:
RootLayout
└── DashboardLayout
└── SettingsPageEach folder contributes its layout if it has one. You never wire this up manually - the file system does it.
When a user navigates from /dashboard to /dashboard/settings, the DashboardLayout does not re-render or remount. Only the page part changes. That means:
This partial rendering is a major performance and UX win over full-page swaps.
Occasionally you want layout-like wrapping that does remount on every navigation - for example, to replay an enter animation or reset per-page state. That is what template.js is for:
// app/dashboard/template.js
'use client';
import { motion } from 'framer-motion';
export default function Template({ children }) {
return (
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }}>
{children}
</motion.div>
);
}A template sits between the layout and the page. Use layouts by default; reach for templates only when remounting is the actual goal.
Combine layouts with route groups from Lesson 3 to give site sections completely different shells:
app/
├── (marketing)/
│ ├── layout.js ← header + footer
│ └── pricing/page.js
└── (app)/
├── layout.js ← sidebar shell
└── dashboard/page.jsBoth sections share the root layout but nothing else. The URLs stay clean: /pricing and /dashboard.
Next up: Lesson 5 shows how users actually move between these routes with the Link component and programmatic navigation.