Layouts and templates

Share a shell across routes without re-rendering it, and know when you want the opposite.

04of 16
≈ 11Min read
7Parts

A layout wraps everything below it

app/layout.tsx wraps every route. app/doors/layout.tsx wraps /doors and everything under it. They nest, outermost first, and the page ends up at the bottom.

app/doors/layout.tsx
export default function DoorsLayout({ children }) {
  return (
    <section className="doors">
      <DoorsNav />
      {children}          {/* the page, or a deeper layout */}
    </section>
  );
}

They do not re-render on navigation

Moving from /doors/12 to /doors/13 renders the page again and leaves the layout alone. Its state survives, its scroll position survives, and a video in it keeps playing.

That is the whole point, and it is also the surprise: a layout cannot read the current route’s parameters, because it is not re-rendered when they change.

what a layout receives
export default function Layout({ children, params }) {
  // params of ITS OWN segment only — app/doors/[id]/layout.tsx gets { id }
  // there is no searchParams here, and no pathname
}

// need the current path in the shell? a small client component:
"use client";
const pathname = usePathname();

Template: the opposite

A template is a layout with a changing key. Use one when something must start fresh on every navigation — and only then, because remounting is work.

app/doors/template.tsx
export default function Template({ children }) {
  return <div className="fade-in">{children}</div>;
}
layout.tsxtemplate.tsx
On navigationStays mountedRemounted
State inside itSurvivesReset
Effects inside itDo not re-runRun again
Use forNavigation, shells, providersEnter animations, per-page logging, resetting a form

Groups give different shells

Neither group appears in the URL. This is the standard way to have a marketing site and an application in one project without the paths growing a prefix nobody wanted.

two shells, one application
app/(marketing)/layout.tsx     ← a wide public shell
app/(marketing)/page.tsx       →  /
app/(marketing)/about/page.tsx →  /about

app/(app)/layout.tsx           ← a sidebar, and an auth check
app/(app)/doors/page.tsx       →  /doors

Metadata belongs to layouts too

a title template, set once
// app/layout.tsx
export const metadata = {
  title: { default: "Doors", template: "%s · Doors" },
};

// app/doors/[id]/page.tsx
export const metadata = { title: "Painted" };     // → "Painted · Doors"

Where providers go

the one client component near the top
// app/providers.tsx
"use client";
export function Providers({ children }) {
  return <ThemeProvider><QueryClientProvider …>{children}</QueryClientProvider></ThemeProvider>;
}

// app/layout.tsx  — still a server component
export default function RootLayout({ children }) {
  return <html lang="en"><body><Providers>{children}</Providers></body></html>;
}
Children passed to a client component stay on the server

The children above are still server components. Wrapping the tree in a provider does not make everything below it a client component — only the provider itself.

What tends to go wrong

  • Putting "use client" on the root layout, which drags the whole application into the browser bundle.
  • Expecting a layout to re-run when the route changes — it does not. Use a client component with usePathname, or a template.
  • Fetching the same data in a layout and in its page. Both run, and fetch deduplicates identical requests — but a database call is made twice.

Practice