~/TechPurAI
~/tutorials/nextjs-from-scratch/layouts
beginner·part 4 of 22·3 min read

Layouts: shared UI across routes

Updated Aug 31, 2026JavaScript · Next.js

Part 1's root layout.tsx has been the bare minimum — <html> and <body>, nothing else. This part adds a real header and footer to it, then a second layout scoped to just the posts section, to show how layouts nest.

Building out the root layout

tsx
// app/layout.tsx
import Link from "next/link";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <header>
          <Link href="/">devnotes</Link>
          <nav>
            <Link href="/posts">Posts</Link>
          </nav>
        </header>
        <main>{children}</main>
        <footer>© 2026 devnotes</footer>
      </body>
    </html>
  );
}

{children} is where the actual matched page renders — everything else in this file (the header, the nav, the footer) now appears on every single route in the project, written exactly once. Navigating from / to /posts re-renders {children}; the header and footer around it don't remount.

A second layout, scoped to one section

tsx
// app/posts/layout.tsx
export default function PostsLayout({ children }: { children: React.ReactNode }) {
  return (
    <div className="posts-section">
      <aside>Browse by tag (coming in a later part)</aside>
      {children}
    </div>
  );
}

Adding layout.tsx inside app/posts/ wraps every route under /posts — the list page, and every individual post page once part 6 adds them — with this sidebar, without touching the root layout at all. It doesn't replace the root layout; both apply, nested: RootLayout wraps PostsLayout, which wraps whichever page.tsx actually matched.

Layouts don't get route params by default

tsx
// app/posts/layout.tsx — this does NOT receive a specific post's data
export default function PostsLayout({ children }: { children: React.ReactNode }) {
  // no access to which specific post, if any, is currently being viewed
  return <div className="posts-section">{children}</div>;
}

A layout wraps every route beneath it, including ones with different dynamic segments — PostsLayout renders identically whether the current page is the post list or one specific post's detail page (part 6). This is deliberate: a layout is for structure shared across an entire section, not per-page content, which is exactly why it doesn't automatically know which specific post is currently open.

Common mistake

Putting page-specific content — a specific post's title, meant only for that one page — inside a layout instead of the page itself. A layout persists across every route it wraps; content that should change per-route belongs in that route's own page.tsx.

FAQ

What's the difference between layout.tsx and template.tsx? A layout.tsx persists across navigations within the routes it wraps — state inside it survives moving between pages, as described above. A template.tsx (rarer, not used in this series) creates a fresh instance on every navigation instead, useful specifically when you need an effect or animation to re-run on each page change rather than staying mounted.

Do loading.tsx and error.tsx work the same way as layout.tsx? They follow the same file-based nesting convention — placed alongside a page.tsx, they apply to that route and everything beneath it — but they serve different purposes: loading.tsx shows automatically while a route's data is being fetched, and error.tsx catches a rendering error in that route segment, neither of which layout.tsx does.

Can a layout fetch its own data? Yes — a layout is just an async Server Component like any page.tsx, so it can await a fetch directly inside it. That data becomes available to the layout's own markup, but still isn't passed down to children — the parent/child relationship in the props is one-directional.

Next: actual data — every page so far has rendered hardcoded text. Time to fetch real posts.

VK

Vijay Kumar

Founder of TechPurAI — writing hands-on tutorials and honest tool breakdowns.

LinkedIn ↗
← previous3. Server Components and Client Components: the default and the escape hatchnext →5. Fetching data in Server Components