React SME Cookbook
All FAQs

Search Documentation

Search across all documentation pages

layoutsroot-layoutnested-layoutstemplatescomposition

Layouts

Layouts wrap pages with shared UI that persists across navigations. They never remount — use templates when you need fresh state.

Recipe

Quick-reference recipe card — copy-paste ready.

// app/layout.tsx — Root layout (required, exactly one)
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <header>Site Header</header>
        <main>{children}</main>
        <footer>Site Footer</footer>
      </body>
    </html>
  );
}
 
// app/dashboard/layout.tsx — Nested layout for /dashboard/*
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
  return (
    <div className="flex">
      <nav className="w-64">Sidebar</nav>
      <section className="flex-1">{children}</section>
    </div>
  );
}

When to reach for this: Whenever multiple pages share the same wrapper UI — navigation bars, sidebars, providers, or structural containers.

Working Example

// app/layout.tsx
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
 
const inter = Inter({ subsets: ["latin"] });
 
export const metadata: Metadata = {
  title: { default: "My App", template: "%s | My App" },
};
 
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <nav className="border-b px-6 py-3">
          <a href="/">Home</a>
          <a href="/dashboard" className="ml-4">Dashboard</a>
        </nav>
        {children}
      </body>
    </html>
  );
}
// app/dashboard/layout.tsx
import Link from "next/link";
 
const sidebarLinks = [
  { href: "/dashboard", label: "Overview" },
  { href: "/dashboard/analytics", label: "Analytics" },
  { href: "/dashboard/settings", label: "Settings" },
];
 
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
  return (
    <div className="flex min-h-screen">
      <aside className="w-64 border-r p-4">
        <h2 className="mb-4 font-bold">Dashboard</h2>
        <ul className="space-y-2">
          {sidebarLinks.map((link) => (
            <li key={link.href}>
              <Link href={link.href} className="text-blue-600 hover:underline">
                {link.label}
              </Link>
            </li>
          ))}
        </ul>
      </aside>
      <main className="flex-1 p-6">{children}</main>
    </div>
  );
}
// app/dashboard/page.tsx
export default function DashboardPage() {
  return <h1>Dashboard Overview</h1>;
}
 
// app/dashboard/analytics/page.tsx
export default function AnalyticsPage() {
  return <h1>Analytics</h1>;
  // Navigating here keeps the sidebar mounted — no remount, no state loss
}

Deep Dive

How It Works

  • Root layout is mandatory. It must contain <html> and <body> tags. Next.js will error if this file is missing.
  • Layouts receive children only. The children prop is the current page or the next nested layout in the tree.
  • Layouts do not remount on navigation. React reconciles them — component state, effects, and DOM persist. This is why sidebars and nav bars stay interactive.
  • Layouts nest automatically. app/layout.tsx wraps app/dashboard/layout.tsx which wraps app/dashboard/page.tsx. You never manually compose them.
  • Layouts are Server Components by default. Add "use client" only when the layout itself needs hooks or browser APIs.
  • Metadata can be defined per layout. Each layout (or page) can export a metadata object or generateMetadata function that merges with parent metadata.

Variations

// Template — remounts on every navigation (useful for enter/exit animations)
// app/dashboard/template.tsx
"use client";
 
import { motion } from "framer-motion";
 
export default function DashboardTemplate({ children }: { children: React.ReactNode }) {
  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.3 }}
    >
      {children}
    </motion.div>
  );
}
// Layout with params — accessing route params in a layout
// app/blog/[slug]/layout.tsx
export default async function BlogPostLayout({
  children,
  params,
}: {
  children: React.ReactNode;
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  return (
    <article>
      <div className="text-sm text-gray-500">Post: {slug}</div>
      {children}
    </article>
  );
}
// Conditional layout — different UI based on auth status
// app/dashboard/layout.tsx
import { auth } from "@/lib/auth";
import { redirect } from "next/navigation";
 
export default async function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const session = await auth();
  if (!session) redirect("/login");
 
  return (
    <div>
      <p>Welcome, {session.user.name}</p>
      {children}
    </div>
  );
}

TypeScript Notes

// Layout props type
interface LayoutProps {
  children: React.ReactNode;
  params: Promise<Record<string, string>>; // async in Next.js 15+
}
 
// Template has the same props as layout
interface TemplateProps {
  children: React.ReactNode;
}
 
// Metadata types
import type { Metadata, ResolvingMetadata } from "next";
 
export async function generateMetadata(
  { params }: { params: Promise<{ slug: string }> },
  parent: ResolvingMetadata
): Promise<Metadata> {
  const { slug } = await params;
  return { title: slug };
}

Gotchas

  • You cannot pass data from layout to children via props. Layouts only receive children. Use React Context, cookies, or a shared data-fetch instead.
  • Root layout cannot be a Client Component without <html> and <body>. Even as a Client Component, it must render those tags.
  • Templates and layouts can coexist. The render order is layout.tsx > template.tsx > page.tsx. The template sits between.
  • Layouts cannot access searchParams. Only page.tsx receives searchParams. If a layout needs query params, use useSearchParams() in a Client Component child.
  • Removing a layout affects all child routes. If you delete app/dashboard/layout.tsx, all dashboard pages lose that wrapper instantly.
  • params are async in Next.js 15+. Always await params in layouts — the synchronous destructuring pattern is deprecated.

Alternatives

ApproachWhen to Use
template.tsxNeed component remount on each navigation (animations, logging)
Route Groups with separate layoutsDifferent layouts for different sections at the same URL depth
Client-side context providerSharing state across pages without a visual wrapper
Parallel Routes (@slot)Rendering multiple pages side-by-side in one layout

Real-World Example

From a production Next.js 15 / React 19 SaaS application (SystemsArchitect.io).

Root Layout with Provider Nesting

// Production example: Root layout with providers and metadata
// File: app/layout.tsx
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import { ThemeProvider } from '@/components/theme-provider';
import { ToastProvider } from '@/components/toast-provider';
import { ProjectStoreInitializer } from '@/components/project-store-initializer';
import './globals.css';
 
const inter = Inter({ subsets: ['latin'] });
 
export const metadata: Metadata = {
  title: { default: 'SystemsArchitect', template: '%s | SystemsArchitect' },
  description: 'Cloud architecture learning platform',
};
 
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body className={inter.className}>
        <ThemeProvider attribute="class" defaultTheme="system" enableSystem>
          <ToastProvider>
            <ProjectStoreInitializer>
              {children}
            </ProjectStoreInitializer>
          </ToastProvider>
        </ThemeProvider>
      </body>
    </html>
  );
}

Async Home Page

// Production example: Home page with server data
// File: app/page.tsx
import { getFeaturedContent } from '@/lib/content';
 
export default async function HomePage() {
  const featured = await getFeaturedContent();
 
  return (
    <main>
      <h1>Welcome to SystemsArchitect</h1>
      {featured.map((item) => (
        <article key={item.id}>{item.title}</article>
      ))}
    </main>
  );
}

What this demonstrates in production:

  • Provider nesting order matters. ThemeProvider wraps everything because toast notifications and the store initializer both need theme context. ToastProvider comes before ProjectStoreInitializer because store initialization errors need to display toast messages.
  • suppressHydrationWarning on the <html> tag is required when using theme providers that set a class or data-theme attribute on the server. Without it, the mismatch between server-rendered and client-rendered class triggers a React warning.
  • Layouts never re-mount on navigation. This means the ThemeProvider, ToastProvider, and ProjectStoreInitializer are initialized once and persist across all page transitions. Any state in these providers is preserved.
  • The metadata.title.template pattern ('%s | SystemsArchitect') enables child pages to set just their title (e.g., export const metadata = { title: 'Dashboard' }) and it automatically renders as "Dashboard | SystemsArchitect".
  • The home page is an async Server Component. It fetches data at request time with zero client JavaScript. This pattern replaces getServerSideProps from the Pages Router.

FAQs

Why is the root layout required?

The root app/layout.tsx must exist and must contain <html> and <body> tags. Next.js errors if this file is missing because it defines the HTML document structure for the entire app.

Do layouts remount when navigating between child routes?

No. Layouts persist across navigations. React reconciles them so component state, effects, and DOM are preserved. This is why sidebars and nav bars stay interactive.

What is the difference between layout.tsx and template.tsx?
  • layout.tsx persists and does not remount on navigation
  • template.tsx remounts on every navigation, giving fresh state
  • Render order is: layout.tsx > template.tsx > page.tsx
  • They can coexist in the same segment
Gotcha: Can you pass data from a layout to its children via props?

No. Layouts only receive children as a prop. Use React Context, cookies, or a shared data-fetch to share data between a layout and its child pages.

Can layouts access searchParams?

No. Only page.tsx receives searchParams. If a layout needs query params, use useSearchParams() in a Client Component child.

How do you access route params in a layout?
// app/blog/[slug]/layout.tsx
export default async function BlogLayout({
  children,
  params,
}: {
  children: React.ReactNode;
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  return (
    <article>
      <p>Post: {slug}</p>
      {children}
    </article>
  );
}
How does the metadata title template work?

Set title: { default: "My App", template: "%s | My App" } in the root layout. Child pages export metadata = { title: "Dashboard" } and it renders as "Dashboard | My App".

Gotcha: What happens if params are destructured synchronously in Next.js 15+?

It breaks. params is now a Promise in Next.js 15+. You must await params in layouts. The synchronous pattern is deprecated.

What are the TypeScript types for layout and template props?
interface LayoutProps {
  children: React.ReactNode;
  params: Promise<Record<string, string>>;
}
 
interface TemplateProps {
  children: React.ReactNode;
}
How do you type generateMetadata in a layout?
import type { Metadata, ResolvingMetadata } from "next";
 
export async function generateMetadata(
  { params }: { params: Promise<{ slug: string }> },
  parent: ResolvingMetadata
): Promise<Metadata> {
  const { slug } = await params;
  return { title: slug };
}
Can the root layout be a Client Component?

Yes, but it must still render <html> and <body> tags. Even as a Client Component, these tags are required.

How do layouts nest automatically?

app/layout.tsx wraps app/dashboard/layout.tsx which wraps app/dashboard/page.tsx. You never manually compose them. Next.js handles the nesting based on the file-system hierarchy.