Back to CourseBeginner9 min read
Next JS /

Pages and Layouts

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

Bishal Biswas

Self Learner

Published on: October 25, 2026
Beginner Topic

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 /books section
  • 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:

  1. It is required. Every app needs one.
  2. It must contain the <html> and <body> tags. No other layout or page should.
  3. You never add a <head> tag by hand. Next.js builds the <head> for you, using the metadata object.

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.title is 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-1 on <main> makes it stretch, so the footer stays at the bottom even on short pages. (The flex flex-col and min-h-full classes 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: metadata only 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

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 as children.
  • 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 params and searchParams. Layouts get children and params. Both are Promises in Next.js 16.
script.js

// 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.