Layouts & Shared UI
A layout is UI that is shared between multiple pages. When a user navigates between pages that share a layout, the layout preserves state, stays interactive, and does not re-render — only the page content below it swaps out. This is one of the most useful performance characteristics of the App Router.
Defining a Layout
A layout is defined by default-exporting a React component from a file named layout.tsx. Every layout must accept and render a children prop — that's where Next.js will inject the matching page or nested layout.
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className="dashboard-shell">
<nav>Dashboard Nav</nav>
<main>{children}</main>
</div>
)
}Every page inside app/dashboard now automatically renders wrapped in this shell — you never import or wire it up manually.
The Root Layout Is Required
Every Next.js App Router project must have a root layout at app/layout.tsx. Unlike every other layout in the tree, the root layout must render the html and body tags itself — Next.js does not add them for you.
// app/layout.tsx
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}Layouts Don't Re-render on Navigation
This is the key behavioral difference from a page. If a user moves from /dashboard/overview to /dashboard/settings, and both live under the same app/dashboard/layout.tsx, React preserves that layout's component instance. Open accordions, scroll position, form input, and any local state inside the layout survive the navigation untouched — only the page content re-renders.
// app/dashboard/layout.tsx
'use client'
import { useState } from 'react'
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
const [collapsed, setCollapsed] = useState(false)
return (
<div>
<button onClick={() => setCollapsed((c) => !c)}>Toggle sidebar</button>
{!collapsed && <aside>Sidebar</aside>}
<main>{children}</main>
</div>
)
}
// The collapsed state above survives navigation between any pages under /dashboard.Layouts Cannot Access Route-Specific Data Directly
Layouts do not receive searchParams as a prop — only pages do. This is intentional: since a layout is shared across many pages (and doesn't re-render on sibling navigation), it shouldn't depend on data that changes per-page, or it would need to re-render on every navigation, defeating the purpose.
What Layouts Are Good For
Persistent navigation — headers, sidebars, tab bars, breadcrumb shells.
Shared providers and context that many pages in a section need.
Consistent page chrome (padding, max-width containers, section headings).
Fetching data that is shared by every page under that route segment.
Layouts are one of the App Router's biggest structural upgrades: shared UI is no longer something you hand-wire into every page — it's a natural consequence of where you place a layout.tsx file in the folder tree.