Learn the difference between pages and layouts in Next.js, give Book Nook a shared header and footer in the root layout, and nest a layout for the books section.

Bishal Biswas
Self Learner
What you will learn
Right now every Book Nook page stands alone. If you want a header with the shop name on every page, you would have to copy it into each page.js. That is a lot of copying, and a lot of places to forget when something changes.
Layouts fix this. In this lesson you will:
- learn the difference between a page and a layout
- give Book Nook a shared header, main area and footer in the root layout
- change the browser tab title with
metadata - add a nested layout that wraps only the
/bookssection - understand why layouts do not re-render when you move between pages
You need the pages from How File-Based Routing Works: /, /about, /books, /books/bestsellers and /cart.
Pages and layouts in one picture
A page is the unique content of one URL. A layout is a frame that sits around pages and stays the same as you move between them.
Think of a picture frame on a wall. You can swap the photo inside (the page) as often as you like, but the frame (the layout) stays on the wall.
┌──────────────────────────────────────┐
│ Header: Book Nook Books About Cart │ ← root layout
├──────────────────────────────────────┤
│ │
│ { page content here } │ ← page.js (changes per URL)
│ │
├──────────────────────────────────────┤
│ Footer │ ← root layout
└──────────────────────────────────────┘
In code, the difference is one prop. A layout receives children, which is the page (or a deeper layout) it wraps:
// A page: returns its own content
export default function AboutPage() {
return <h1>About Book Nook</h1>;
}
// A layout: puts children inside a frame
export default function SomeLayout({ children }) {
return (
<div>
<p>This frame stays the same</p>
{children}
</div>
);
}
The root layout
You already have one layout: app/layout.js. Because it sits at the top of app, it is the root layout, and it wraps every page of the site. Here is the important part of the file create-next-app made:
// app/layout.js (as generated, fonts left out)
export const metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({ children }) {
return (
<html
lang="en"
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
>
<body className="min-h-full flex flex-col">{children}</body>
</html>
);
}
The root layout has three special rules:
- It is required. Every app needs one.
- It must contain the
<html>and<body>tags. No other layout or page should. - You never add a
<head>tag by hand. Next.js builds the<head>for you, using themetadataobject.
Step 1: add a header and footer
Open app/layout.js and replace the metadata and the RootLayout function. Keep the font imports and font setup at the top exactly as they are:
// app/layout.js
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata = {
title: "Book Nook",
description: "A small online bookshop with big stories.",
};
export default function RootLayout({ children }) {
return (
<html
lang="en"
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
>
<body className="min-h-full flex flex-col">
<header className="border-b px-6 py-4">
<nav className="flex gap-6">
<a href="/" className="font-bold">Book Nook</a>
<a href="/books">Books</a>
<a href="/about">About</a>
<a href="/cart">Cart</a>
</nav>
</header>
<main className="mx-auto w-full max-w-3xl flex-1 p-8">{children}</main>
<footer className="border-t px-6 py-4 text-sm text-zinc-500">
Book Nook: a small shop for big stories.
</footer>
</body>
</html>
);
}
Save and visit a few pages. Every page now has the same header and footer, and you wrote them only once.
Here is what changed:
metadata.titleis now"Book Nook", so the browser tab says Book Nook instead of “Create Next App”.- The
<body>now holds a<header>, a<main>and a<footer>. {children}sits inside<main>. That is where each page appears.flex-1on<main>makes it stretch, so the footer stays at the bottom even on short pages. (Theflex flex-colandmin-h-fullclasses from the generated file make this work.)
For now the menu uses plain <a> tags. They work, but each click reloads the whole page. In Linking and Navigating Between Pages you will swap them for the faster <Link> component. (npm run lint already complains about them, which is a good hint.)
Step 2: tidy up your pages
The layout now provides the <main> tag and the padding, so pages should not add their own. Two <main> tags on one page is not valid HTML. Change the outer <main> of each page to a <section> and drop the spacing classes:
// app/about/page.js
export default function AboutPage() {
return (
<section>
<h1 className="text-3xl font-bold">About Book Nook</h1>
<p className="mt-4">
Book Nook is a small, independent bookshop. We pick every book by hand
and ship it with care.
</p>
</section>
);
}
Do the same in app/page.js, app/books/page.js, app/books/bestsellers/page.js and app/cart/page.js. This is the nice thing about layouts: the page only cares about its own content.
Step 3: give a page its own title
Every page can export its own metadata. It is merged with the layout’s metadata, and the page wins when both set the same field:
// app/about/page.js
export const metadata = {
title: "About | Book Nook",
};
export default function AboutPage() {
// ...same as before
}
Now the tab says About | Book Nook on /about, and Book Nook everywhere else. There is much more you can do with metadata, like descriptions for search engines and title templates. That is all in Page Titles, Descriptions and Metadata.
Note:
metadataonly works in Server Components, which is what pages and layouts are by default. You will learn what that means in Server Components Explained.
Step 4: add a nested layout for the books section
Layouts can be nested. A layout.js inside a folder wraps every page in that folder and its subfolders, but nothing else.
Let us give the books section its own frame: a small banner and a side menu. Create app/books/layout.js:
// app/books/layout.js
export default function BooksLayout({ children }) {
return (
<div>
<p className="mb-6 rounded bg-amber-100 px-4 py-2 text-amber-900">
Free delivery on orders over $25.
</p>
<div className="flex gap-8">
<aside className="w-40 shrink-0">
<h2 className="font-semibold">Browse</h2>
<ul className="mt-2 space-y-1 text-sm">
<li><a href="/books">All books</a></li>
<li><a href="/books/bestsellers">Bestsellers</a></li>
</ul>
</aside>
<div className="flex-1">{children}</div>
</div>
</div>
);
}
Visit /books and /books/bestsellers: both show the banner and side menu. Now visit /about or /cart: no banner, no side menu. The books layout only applies inside the books folder.
Notice that this layout has no <html> or <body>. Only the root layout has those. A nested layout returns ordinary elements, like a <div>.
How layouts nest
When someone visits /books/bestsellers, Next.js collects every layout.js on the way down, and wraps them around the page like boxes inside boxes:
app/layout.js RootLayout
└── app/books/layout.js BooksLayout
└── app/books/bestsellers/page.js BestsellersPage
The result is the same as if you had written:
<RootLayout>
<BooksLayout>
<BestsellersPage />
</BooksLayout>
</RootLayout>
Here is which layouts wrap which pages in Book Nook now:
| URL | Wrapped by |
|---|---|
/ |
root layout |
/about |
root layout |
/cart |
root layout |
/books |
root layout, then books layout |
/books/bestsellers |
root layout, then books layout |
Layouts do not re-render
This is the key difference between layouts and pages. When you move from one page to another with <Link> (next lesson), Next.js only swaps the page. The layouts around it stay exactly as they are: they keep their state, stay interactive, and do not re-render or re-fetch anything.
That has some nice results:
- A search box or a playing music player in a layout keeps its value when you change pages.
- Navigation is faster, because less has to be rebuilt.
It also has one consequence to remember: because a layout does not re-render, it cannot know the current URL or its ?search= part directly. If a layout needs to highlight the current menu item, that small part must be a Client Component that reads the URL with a hook. You will build exactly that in Linking and Navigating Between Pages.
If you ever need a frame that does reset on every navigation, Next.js has template.js for that. See Templates and the Root Layout in Depth.
What pages and layouts receive
Pages and layouts are called by Next.js, not by you, so Next.js decides what props they get:
| Component | Props |
|---|---|
layout.js |
children, and params for dynamic routes |
page.js |
params and searchParams |
childrenis what the layout wraps.paramsholds the dynamic parts of the URL, like thedunein/books/dune. See Dynamic Routes and params.searchParamsholds the part after?, like?sort=price. Only pages get it. See Reading Search Params from the URL.
In Next.js 16, params and searchParams are Promises, so you await them. You will do that in the next lessons.
Common mistakes
Forgetting {children}
If your layout returns a frame but no {children}, every page inside it shows only the frame, with no content. There is no error message, just missing pages:
// app/books/layout.js (broken: the page never appears)
export default function BooksLayout() {
return <aside>Browse</aside>;
}
Accept { children } as a prop and place it where the page should go.
Adding html and body to a nested layout or page
Only the root layout should render <html> and <body>. Putting them in app/books/layout.js or a page creates a page inside a page, and React warns that <html> cannot be a child of <main>. Return a <div>, <section> or a fragment (<>...</>) instead.
Writing a head tag by hand
Adding <head><title>Book Nook</title></head> to the root layout is the old way. Use export const metadata = { title: 'Book Nook' } instead. Next.js builds the head for you and merges titles from layouts and pages correctly.
Two main tags on one page
If the layout has <main> and the page also starts with <main>, the HTML is invalid and screen readers get confused. Let the layout own <main>, and use <section> or a fragment in pages.
Quick recap
- A page (
page.js) is the unique content of one URL. A layout (layout.js) wraps pages with shared UI and receives them aschildren. - The root layout (
app/layout.js) is required, wraps every page, and is the only place for<html>and<body>. export const metadata = { title: '...' }sets the tab title. Pages can override the layout’s metadata.- A layout inside a folder, like
app/books/layout.js, wraps only the pages in that folder and below. - Layouts nest like boxes: root layout, then section layouts, then the page.
- Layouts keep their state and do not re-render on navigation, so they cannot read the current URL directly.
- Pages get
paramsandsearchParams. Layouts getchildrenandparams. Both are Promises in Next.js 16.
// app/layout.js: the Book Nook root layout (wraps every page)
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"] });
const geistMono = Geist_Mono({ variable: "--font-geist-mono", subsets: ["latin"] });
// Sets the browser tab title and the description for search engines
export const metadata = {
title: "Book Nook",
description: "A small online bookshop with big stories.",
};
// Only the root layout renders html and body
export default function RootLayout({ children }) {
return (
<html lang="en" className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}>
<body className="min-h-full flex flex-col">
<header className="border-b px-6 py-4">
{/* Plain links for now: the next lesson swaps them for Link */}
<nav className="flex gap-6">
<a href="/" className="font-bold">Book Nook</a>
<a href="/books">Books</a>
<a href="/about">About</a>
<a href="/cart">Cart</a>
</nav>
</header>
{/* Each page appears here */}
<main className="mx-auto w-full max-w-3xl flex-1 p-8">{children}</main>
<footer className="border-t px-6 py-4 text-sm text-zinc-500">
Book Nook: a small shop for big stories.
</footer>
</body>
</html>
);
}
// --- next file: app/books/layout.js (wraps /books and everything below it)
// export default function BooksLayout({ children }) {
// return (
// <div className="flex gap-8">
// <aside className="w-40 shrink-0">Browse</aside>
// <div className="flex-1">{children}</div>
// </div>
// );
// }
Was this explanation helpful?
Your feedback helps us improve the content for everyone.
Thank you for your feedback!
We appreciate you helping us improve.
What could we improve? (Optional)
Read aloud
Ready