Nested Layouts
How layouts stack inside each other by folder depth, and why that keeps state alive across navigations.
阅读需 2 分钟
Layouts in the App Router nest automatically by folder depth. A layout.tsx inside app/blog/ wraps every route under /blog, and it in turn is wrapped by the root layout. You don't register this relationship anywhere — it falls directly out of where the files live.
How nesting works
app/
├── layout.tsx # wraps everything
├── page.tsx # "/"
└── blog/
├── layout.tsx # wraps everything under /blog
├── page.tsx # "/blog"
└── [slug]/
└── page.tsx # "/blog/my-post"Visiting /blog/my-post renders RootLayout → BlogLayout → BlogPostPage, each nested inside the one above via children:
// app/blog/layout.tsx
export default function BlogLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="blog-shell">
<aside>Blog categories, recent posts, etc.</aside>
<div className="blog-content">{children}</div>
</div>
);
}Notice this layout doesn't render <html> or <body> — only the root layout does that. Every other layout just returns whatever UI it wants to wrap around its children.
Why layouts don't re-render on navigation
This is the detail that makes layouts genuinely useful rather than just a nesting convenience: when you navigate from /blog/post-a to /blog/post-b, BlogLayout is not re-mounted. React preserves its state and doesn't re-run its effects, because both URLs resolve to the same layout component in the tree. Only the segment that actually changed — the page itself — re-renders.
That means things like a sidebar's scroll position, an open/closed mobile-nav toggle, or a video that's mid-playback in a layout survive navigation between sibling pages. If you've built a React SPA before, this is the sort of behavior you'd normally hand-roll with careful component placement — here it's the default.
Layouts don't receive route params by default
A layout only gets a params prop if its own route segment is dynamic. A layout.tsx sitting at app/dashboard/[teamId]/layout.tsx receives params, but a layout further up the tree at app/dashboard/layout.tsx does not, since dynamic segments below it aren't part of its own path yet:
// app/dashboard/[teamId]/layout.tsx
export default async function TeamLayout({
children,
params,
}: {
children: React.ReactNode;
params: Promise<{ teamId: string }>;
}) {
const { teamId } = await params;
return (
<div>
<nav>Team: {teamId}</nav>
{children}
</div>
);
}Layouts can't pass data down via props to pages
One limitation worth internalizing early: a layout can't hand data to the page it wraps through props — children is opaque, already-rendered React content by the time the layout sees it. If a layout and a page both need the same data (say, the current user), each fetches it independently. Next.js automatically deduplicates identical fetch calls made during the same render, so this is cheaper than it sounds — a topic the data-fetching lessons cover in detail.