Layouts and pages
Build the root layout and nested layouts, see in real Chrome how a layout keeps its state between pages, and learn what LayoutProps and PageProps give you.
- 01Write the root layout and nested layoutsGive every page its html and body, and give one folder's pages a frame of their own.
- 02Say what stays on a clickTell which state survives a navigation: a layout's, a page Next.js kept hidden, or none after a full page load.
- 03Use LayoutProps and PagePropsKnow what each one receives, and read the query and the path where a layout can't.
A frame around every page
In File-system routing you saw that a page.tsx in a folder makes an address a page. A page is the part of the screen that belongs to one address. A layout is the part that several pages share, like a header with the shop's links. You write it once, in a file named layout.tsx, and Next.js puts each page inside it.
Here is the tea shop with one layout and two pages. Like every example in this lesson, it's a real Next.js 16.4.3 project. It was built with next build and run with next start, and what it sent back was recorded. Read app/layout.tsx first, then open the Browser tab and switch between / and /about.
app/layout.tsximport Link from "next/link"; export const metadata = { title: "Tea shop" }; export default function RootLayout({ children }: LayoutProps<"/">) { return ( <html lang="en"> <body> <header> <Link href="/">Home</Link> <Link href="/teas">Teas</Link> <Link href="/about">About</Link> </header> {children} <footer>Open every day, 9 to 5.</footer> </body> </html> );}Fresh tea, sent to your door.
The page as the browser shows it when its HTML arrives, drawn with plain styles. Its JavaScript doesn't run here.
app/layout.tsx is the root layout: the layout in the app folder itself, around every page of the app. Its component gets a prop called children, on line 5, and line 12 says where it goes. Above it, lines 9 to 11 are a <header> with three links. Below it, line 13 is a <footer>. Line 3 exports metadata, and its title becomes the page's <title>: the browser frame shows Tea shop.
Now look at the pages. / shows Home Teas About, then Tea shop and Fresh tea, sent to your door., then Open every day, 9 to 5. /about has the same first and last lines, with About us in between. Neither app/page.tsx nor app/about/page.tsx mentions a header or a footer. For each address, Next.js renders the page and passes it to the layout as children. The Network tab shows it in the HTML: the <header>, then the page's <main>, then the <footer>, all inside one <body>.
app/layout.tsx, the root layout: the same header and footer on every page. The part in between is the page for the address, which the layout shows where it puts {children}.A shop hangs one frame on the wall and changes the picture inside it every week. Nobody takes the frame down to do it: it stays on its nail, and a note taped to its corner stays with it. Only the picture changes.
- In real life: The frame that hangs on the wall
- In Next.js: A layout, around the pages
- In real life: The picture you slide into it
- In Next.js: The page for the current address:
children - In real life: Swapping in a new picture, while the frame stays on its nail
- In Next.js: A click on a
Link: the page changes, the layout stays - In real life: A note taped to the frame, still there after each swap
- In Next.js: State in the layout, like a counter, still there after each click
A page is what one address shows; a layout is what several addresses share. A layout.tsx goes around every page in its folder and the folders below it, with the page where it puts children. When a visitor clicks a Link to another page under the same layout, the layout stays on the screen, and keeps its state.
The root layout: html and body
Every App Router app has a root layout, and it has one job no other file has: it renders the <html> and <body> tags. In the shop, lines 7 and 8 open them, and every page ends up inside that one <body>. The lang="en" on line 7 tells browsers and screen readers the page is in English.
You don't write a <head>. Next.js writes it for you, with the <title> from metadata and the <script> tags the page needs. The Network tab shows them all inside <head>, though the layout never mentions it. (Titles and link previews have their own lesson, The Metadata API.)
What if the tags are missing, or the whole file is? Each version below was built with real Next.js. Guess first, then click.
app/layout.tsximport Link from "next/link"; export const metadata = { title: "Tea shop" }; export default function RootLayout({ children }: LayoutProps<"/">) { return ( <html lang="en"> <body> <header> <Link href="/">Home</Link> <Link href="/teas">Teas</Link> <Link href="/about">About</Link> </header> {children} <footer>Open every day, 9 to 5.</footer> </body> </html> );}$ next build▲ Next.js 16.4.3 (Turbopack)- Cache Components enabled- Partial Prefetching enabled Creating an optimized production build ...✓ Compiled successfully Running TypeScript ... Finished TypeScript ... Collecting page data ...✓ Generating static pages (4/4) Finalizing page optimization ... Route (app)┌ ○ /├ ○ /_not-found└ ○ /about ○ (Static) prerendered as static contentLines 7 and 8 give every page its <html> and <body>. The build lists ○ / and ○ /about: both pages, inside the layout, prerendered as static content.
With the tags, the build lists ○ / and ○ /about. ○ means prerendered as static content: the pages' HTML was made during the build. Without the tags, the production build still passes, and Chrome shows the page with no errors, because a browser adds missing <html> and <body> tags while it reads a page. But don't count on that. While you develop, next dev checks for the tags and shows the error Missing <html> and <body> tags in the root layout. You can read that check in Next.js's source: createRootLayoutValidatorStream, which app-render.tsx turns on only in the development server.
With no app/layout.tsx at all, the build stops with an error for each page: page.tsx doesn't have a root layout. To fix this error, make sure every page has a root layout. So a root layout isn't optional. A new project from create-next-app already has one.
Layouts inside folders
The root layout isn't the only place a layout can go. A layout.tsx in any folder goes around the pages in that folder and in the folders below it. The tea pages want a menu of teas that the other pages don't show, so app/teas gets a layout of its own. A layout inside a folder is a nested layout.
app/teas/layout.tsximport Link from "next/link"; export default function TeasLayout({ children }: LayoutProps<"/teas">) { return ( <section> <nav> <Link href="/teas/green">Green tea</Link> <Link href="/teas/mint">Mint tea</Link> </nav> {children} </section> );}Light and fresh. $4 a box.
The page as the browser shows it when its HTML arrives, drawn with plain styles. Its JavaScript doesn't run here.
app/teas/layout.tsx has the same shape as the root layout, without <html> and <body>: only the root layout renders those. Line 3 types its props as LayoutProps<"/teas">, the layout of the /teas folder. It returns a <section> on line 5 with a <nav> of two tea links on lines 6 to 8, and the page on line 9.
Open the Browser tab and read /teas/green from the top: the root layout's header, then the tea layout's menu, Green tea Mint tea, then the page, then the root layout's footer. Now pick /about: the header, the page and the footer, and no tea menu. /about isn't in the app/teas folder, so the tea layout never wraps it. In the Network tab, the HTML of /teas/green shows the nesting itself: the page's <main> sits inside the tea layout's <section>, which sits inside the root layout's <body>.
/teas/green is inside app/teas, so the root layout wraps the tea layout, which wraps the page. /about isn't in app/teas, so only the root layout wraps it.Each row is one address in this shop: its page file, and the layouts that wrap it, from the outside in.
| Address | Page file | Layouts around it |
|---|---|---|
/ | app/page.tsx | The root layout only. |
/about | app/about/page.tsx | The root layout only. |
/teas | app/teas/page.tsx | The root layout, then the tea layout. |
/teas/green | app/teas/green/page.tsx | The root layout, then the tea layout. |
/teas gets the tea layout too: a layout wraps the page.tsx in its own folder as well as the pages below. The Terminal tab lists every route as ○. Layouts don't add routes; only page files do.
Layouts keep their state between pages
Layouts do more than save typing. When a visitor clicks a Link to a page under the same root layout, as every page in this shop is, Next.js makes a client-side navigation: the page's JavaScript changes the address and swaps the page part, without loading a whole new HTML page. Next.js's docs say that on such a navigation, layouts keep their state, stay interactive and don't render again. You can watch it happen.
For that, the tea layout gets a cup counter. It needs state and a click handler, so it's a Client Component: app/teas/brew-counter.tsx starts with "use client", which marks where the part of the app that also runs in the browser begins. The layout and the pages stay Server Components: they run only on the server, and the browser gets what they rendered. (Server Components explains the two kinds.)
Clicks need a browser with the page's JavaScript running, so these recordings were made in real Chrome. Each version was opened at /teas/green, its Brew a cup button was pressed twice, then the Mint tea link was clicked, then the Green tea link. The Steps tab lists the steps: pick one to see the page right after it, and the requests the page made. Before you pick a version, guess what each counter shows after the click on Mint tea.
app/teas/layout.tsximport Link from "next/link";import BrewCounter from "./brew-counter"; export default function TeasLayout({ children }: LayoutProps<"/teas">) { return ( <section> <nav> <Link href="/teas/green">Green tea</Link> <Link href="/teas/mint">Mint tea</Link> </nav> <BrewCounter /> {children} </section> );}Each step was taken in real Chrome against next start. Pick one to see the page after it.
Cups brewed: 2
Cool and sweet. $3 a box.
- Focus
- the link “Mint tea”
- Scrolled
- 0 px down
No requests during this step: the page already had what it needed
The counter is in the tea layout, line 10. Brew two cups on Green tea, then click Mint tea: it still says 2, because the layout stayed on the screen. Click Home, then Teas: the tea layout comes back with 2, because Next.js kept it hidden while you were away.
In the first version, step 4 shows Mint tea and Cups brewed: 2. The layout stayed on the screen, and only its children changed, so BrewCounter kept its state. Steps 2 and 3 made no requests at all: pressing Brew a cup only updated state in the browser. Step 4 made none either: when the Mint tea link appeared, step 1 had already fetched that page's static shell in the background, which for a page that's all static is the whole page. That's called prefetching, and the next lesson is about it.
2. Only the page part changed. Next.js kept the Green tea page hidden, not on the screen, so it can come back as it was.The second version puts a counter in each page instead. After the click, Mint tea's counter says 0: it's a different component, in a different page, with state of its own. Step 5 is a surprise if you know older Next.js: back on Green tea, the counter says 2 again. Cache Components is Next.js 16's model for caching and rendering, turned on with cacheComponents: true in next.config.ts, as new projects have it; the Rendering and caching stage teaches it. With it on, Next.js doesn't throw away a page you leave. It hides it with React's Activity, which keeps its state, and shows it again when you come back. At each level of folders, it keeps the page on the screen plus up to two pages you left: in bfcache-state-manager.ts, MAX_BF_CACHE_ENTRIES is 3 with Cache Components on, counting the visible one. The docs say that before Cache Components, a page's state was lost when you left it: only layouts kept theirs.
The third version uses plain <a> tags. Every click shows a document request in the Steps tab: the browser loaded a whole new HTML page, and everything on the old one was thrown away, so the counter is back at 0. A layout keeps its state only across client-side navigations, which is one reason to use Link for your own pages.
So the place decides who shares the state. A Client Component in a layout keeps one state for every page under that layout. A Client Component in a page keeps a state for that page alone. Go back to the first version and look at steps 6 and 7: after Home, the tea layout isn't on the screen, and after Teas it's back with Cups brewed: 2, because Next.js kept it hidden too.
What layouts and pages receive
Next.js calls your layouts and pages with props. You've used children. To type the props, every example in this lesson uses two types: LayoutProps and PageProps. You never import them. Next.js writes them for your app when it runs next dev, next build or next typegen, from your folders, so it knows every route. They arrived in Next.js 15.5. Between the angle brackets goes the route, as a string: LayoutProps<"/teas"> for the tea layout, PageProps<"/teas"> for the tea page.
Each row is one prop: whether a layout gets it, and whether a page gets it.
| Prop | Does a layout get it? | Does a page get it? |
|---|---|---|
children | Yes: the page, or the next layout down. | No. A page is the last part; nothing goes inside it. |
params | Yes: the dynamic parts of the address above it. {} for the tea shop's folders. | Yes: every dynamic part of the address. {} here too. |
searchParams | No. A layout doesn't get the query. | Yes: the query after ?, like sort=price. |
| The current path | No. | No. Read it with usePathname in a Client Component. |
params holds the parts of the address that come from a dynamic segment, a folder with a name in square brackets that matches many addresses. The shop has none yet, so it's {}; Dynamic routes fills it. searchParams holds the query: the part of an address after ?, like sort=price in /teas?sort=price. Both are promises, so you await them. (In Next.js 14 and earlier they were plain objects; Next.js 15 made them promises.)
So the rule is short: a page gets searchParams, and a layout doesn't. The next section shows what happens when a layout asks for it anyway. How a page reads the query, and what that changes about when the page is made, is the Search params and URL state lesson, later in this stage.
What a layout can't read
The table above has two "No" answers for layouts: the query and the current path. Both come from the same fact. A layout doesn't render again when the visitor moves between its pages, so a query or a path it read would soon be out of date. Next.js doesn't give it either. Try to read the query in the tea layout anyway:
app/teas/layout.tsximport Link from "next/link"; export default async function TeasLayout({ children, searchParams }: LayoutProps<"/teas">) { const { sort } = await searchParams; return ( <section> <nav> <Link href="/teas/green">Green tea</Link> <Link href="/teas/mint">Mint tea</Link> </nav> <p>Sorted by: {sort}</p> {children} </section> );}$ next build▲ Next.js 16.4.3 (Turbopack)- Cache Components enabled- Partial Prefetching enabled Creating an optimized production build ...✓ Compiled successfully Running TypeScript ...app/teas/layout.tsx(3,54): error TS2339: Property 'searchParams' does not exist on type 'LayoutProps<"/teas">'.Failed to type check.The build stops while it checks types: Property 'searchParams' does not exist on type 'LayoutProps<"/teas">'. The (3,54) in the message points at line 3, where the layout asks for searchParams. It isn't only TypeScript being strict: written in plain JavaScript, with nothing to check its types, the same layout builds, and /teas?sort=price shows searchParams is undefined. The prop simply isn't there.
app/teas/layout.jsximport Link from "next/link"; export default function TeasLayout({ children, searchParams }) { return ( <section> <nav> <Link href="/teas/green">Green tea</Link> <Link href="/teas/mint">Mint tea</Link> </nav> <p>searchParams is {String(searchParams)}</p> {children} </section> );}searchParams is undefined
Pick a tea from the menu.
The page as the browser shows it when its HTML arrives, drawn with plain styles. Its JavaScript doesn't run here.
For the path, there's no prop either. To show it in a layout, put a Client Component inside the layout that calls usePathname from next/navigation. Client Components render again on a navigation, so they always have the current path.
app/teas/you-are-here.tsx"use client"; import { usePathname } from "next/navigation"; export default function YouAreHere() { const pathname = usePathname(); return <p>You are at {pathname}</p>;}Each step was taken in real Chrome against next start. Pick one to see the page after it.
You are at /teas/mint
Cool and sweet. $3 a box.
- Focus
- the link “Mint tea”
- Scrolled
- 0 px down
No requests during this step: the page already had what it needed
app/teas/you-are-here.tsx starts with "use client" on line 1. Line 6 calls usePathname(), which returns the path without the query, like /teas/green, and line 7 shows it. The tea layout puts <YouAreHere /> above the page. The Steps tab shows the recording from Chrome: step 1 opens /teas/green and the page says You are at /teas/green; after the click on Mint tea, in step 2, it says You are at /teas/mint. The layout didn't render again, but the Client Component inside it did.
Each row is something a layout can't read, and what to use instead.
| What you need | Where to get it |
|---|---|
The query, like ?sort=price | The page's searchParams prop, or useSearchParams in a Client Component. |
The current path, like /teas/mint | usePathname in a Client Component, placed inside the layout. |
| Data for the page below it | Load it again in the page. A layout can't pass props to children. |
The last row is about data. A layout can't pass props to the page in children: Next.js renders the page, not your layout. If the layout and the page both need the same data, each one loads it; Parallel and sequential fetching shows how to avoid loading it twice.
Planning the tea shop's layouts
When you add a part to an app, ask who shows it. If every page shows it, it goes in the root layout. If the pages of one folder show it, it goes in that folder's layout. If one address shows it, it goes in that page. And if it needs the query, it goes in a page, because layouts don't get one. Sort these parts of the tea shop:
- The
<html>and<body>tags. - The shop's header with links to Home, Teas and About.
- A menu of teas that only the tea pages show.
- Green tea's description and price.
- The tea list in the order
?sort=priceasks for. - A cup counter that keeps its count while the visitor moves between tea pages.
- The About us heading.
Sort each part of the tea shop: does it belong in a layout, or in a page?
One more case: some parts sit around a group of pages that don't share a folder in the address, like a sign-in area. Route groups, folders in brackets like (shop), give them a layout without changing any address: Route groups and private folders shows how.
Common mix-ups
- "Each page needs its own header." No. Put it in a layout once; the recorded
/and/aboutboth show the root layout's header. - "The root layout is optional." No. Without
app/layout.tsx,next buildstops: page.tsx doesn't have a root layout. - "Every layout renders
<html>and<body>." No. Only the root layout does. A nested layout returns ordinary elements, like the tea layout's<section>. - "A layout renders again on every click." No. On a client-side navigation it stays on the screen; its counter kept
2from Green tea to Mint tea. - "A layout keeps its state whatever the link." No. A plain
<a>loads a whole new page, and the counter went back to0. - "A layout can read the query or the path." No.
LayoutPropshas neither. Read the query in the page, and the path withusePathnamein a Client Component. - "A nested layout replaces the root layout." No. It sits inside it:
/teas/greenshows both the header and the tea menu.
Practice exercises
These exercises are about Next.js projects, which can't run inside this page. Answer first, then use the hints.
These are the tea shop's files. Which layout.tsx files wrap the page at /teas/green?
app/
about/
page.tsx
teas/
green/
page.tsx
mint/
page.tsx
layout.tsx
page.tsx
layout.tsx
page.tsxBoth: app/layout.tsx and app/teas/layout.tsx. The recorded /teas/green shows the root layout's header and footer and the tea layout's menu around the page.
This is app/about/page.tsx, inside the shop's root layout. What's the last line of text on /about?
export default function AboutPage() {
return (
<main>
<h1>About us</h1>
<p>Asha and Ravi run the shop.</p>
</main>
);
}
Open every day, 9 to 5. The root layout renders its footer after children, on line 13, so the footer comes after the page. The recorded /about ends with that line.
The shop adds /account/orders and /account/address. Write app/account/layout.tsx with links to both, above the page. Then: does /about show those links?
// app/account/layout.tsx
export default function AccountLayout({ children }: LayoutProps<"/account">) {
// Return a <section> with links to /account/orders and /account/address,
// then the page.
}import Link from "next/link";
export default function AccountLayout({ children }: LayoutProps<"/account">) {
return (
<section>
<nav>
<Link href="/account/orders">Orders</Link> <Link href="/account/address">Address</Link>
</nav>
{children}
</section>
);
}
No. The account layout wraps only /account/orders and /account/address. The recorded build of the shop with this file shows Orders Address above Your orders on /account/orders, and no account links on /about.
Each tea page has a counter, like this Green tea page. A visitor presses Brew a cup twice on /teas/green, then clicks the Mint tea link. What does Mint tea's counter show?
import BrewCounter from "../brew-counter";
export default function GreenTeaPage() {
return (
<main>
<h1>Green tea</h1>
<p>Light and fresh. $4 a box.</p>
<BrewCounter />
</main>
);
}
0. Each page has its own BrewCounter, with its own state. The recording in Chrome shows Cups brewed: 0 on Mint tea, and Cups brewed: 2 again when the visitor goes back to Green tea, which Next.js kept hidden.
A teammate wants the teas sorted by ?sort=price, and wrote this tea layout. The build fails with Property 'searchParams' does not exist on type 'LayoutProps<"/teas">'. Which file should read searchParams?
import Link from "next/link";
export default async function TeasLayout({ children, searchParams }: LayoutProps<"/teas">) {
const { sort } = await searchParams;
return (
<section>
<nav>
<Link href="/teas/green">Green tea</Link> <Link href="/teas/mint">Mint tea</Link>
</nav>
<p>Sorted by: {sort}</p>
{children}
</section>
);
}
import { Suspense } from "react";
const teas = [
{ name: "Green tea", price: 4 },
{ name: "Mint tea", price: 3 },
{ name: "Lemon tea", price: 5 },
];
async function TeaList({ searchParams }: { searchParams: PageProps<"/teas">["searchParams"] }) {
const { sort } = await searchParams;
const shown = sort === "price" ? [...teas].sort((a, b) => a.price - b.price) : teas;
return (
<ul>
{shown.map((tea) => (
<li key={tea.name}>
{tea.name}: ${tea.price}
</li>
))}
</ul>
);
}
export default function TeasPage({ searchParams }: PageProps<"/teas">) {
return (
<main>
<h1>Our teas</h1>
<Suspense fallback={<p>Loading teas…</p>}>
<TeaList searchParams={searchParams} />
</Suspense>
</main>
);
}
The page, app/teas/page.tsx. Only pages get searchParams. Moved there, it builds, and the recorded /teas?sort=price lists Mint tea first. The Search params and URL state lesson explains the rest of this code.
With these files, next build stops with an error for both pages. Which file is missing?
app/
about/
page.tsx
page.tsxexport default function HomePage() {
return (
<main>
<h1>Tea shop</h1>
<p>Fresh tea, sent to your door.</p>
</main>
);
}
app/layout.tsx, the root layout. The recorded build of these two pages stopped with: page.tsx doesn't have a root layout. To fix this error, make sure every page has a root layout.
Check your understanding
For each question, ask which folder each file is in, and whether the click loads a whole new page.
Question 1 of 7What must the root layout,
app/layout.tsx, render aroundchildren?Choose an answer to see the explanation.
Question 2 of 7The cup counter is in
app/teas/layout.tsx. A visitor brews two cups on/teas/green, then clicks the Mint tea link. What does the counter show?Choose an answer to see the explanation.
Question 3 of 7The tea menu uses plain
<a href>tags instead ofLink. What happens to the counter on a click?Choose an answer to see the explanation.
Question 4 of 7This tea layout reads
searchParams. What doesnext builddo with it?Read the code, then predictTSXimport Link from "next/link"; export default async function TeasLayout({ children, searchParams }: LayoutProps<"/teas">) { const { sort } = await searchParams; return ( <section> <nav> <Link href="/teas/green">Green tea</Link> <Link href="/teas/mint">Mint tea</Link> </nav> <p>Sorted by: {sort}</p> {children} </section> ); }Choose an answer to see the explanation.
Question 5 of 7How can the tea layout show the visitor's current path, like
You are at /teas/mint, and keep it right after every click?Choose an answer to see the explanation.
Question 6 of 7In the shop with the tea layout, which layouts wrap the page at
/about?Choose an answer to see the explanation.
Question 7 of 7This root layout returns a
<div>instead of<html>and<body>. What happens?Read the code, then predictTSXexport default function RootLayout({ children }: LayoutProps<"/">) { return <div>{children}</div>; }Choose an answer to see the explanation.
Key takeaways
- A page is what one address shows. A layout is what the pages in its folder, and the folders below, share. It puts the page where it renders
children. - The root layout,
app/layout.tsx, is required and renders<html>and<body>. Next.js writes the<head>. - Layouts nest like their folders: the root layout wraps the tea layout, which wraps the tea pages.
- On a client-side navigation, a layout stays on the screen and its Client Components keep their state. With Cache Components, Next.js also keeps up to two pages you left hidden at each level of folders, with their state. A plain
<a>loads a new page and keeps nothing. LayoutPropsgiveschildrenandparams;PagePropsgivesparamsandsearchParams, both promises. Next.js writes both types for you.- A layout can't read the query or the current path. Read the query in the page, and the path with
usePathnamein a Client Component.
Remember the one-liner.
The layout is the frame that stays; the page is the picture that changes.
LayoutProps and PageProps are written by typegen.ts, lines 1026 to 1046, at release v16.4.3: params and searchParams for a page, params and children for a layout, and nothing else.
Coming next: Linking and navigating. It shows what Link prefetches, how to mark the current page's link, how to navigate from code, and where the scroll and the focus go after a click.