Skip to main content

Layouts and templates in Next.js

Layouts and templates in Next.js App Router are both wrapper components for child pages. The difference is one file name and a big behavioral gap: a layout stays mounted across page transitions, a template remounts every single time.

Theory

TL;DR

  • Layout = house walls (stay while rooms change); template = pop-up tent (rebuilt on every move)
  • Main difference: layouts preserve useState and skip re-renders; templates reset everything
  • Use layouts for navbars, sidebars, and auth providers (the default for 99% of cases)
  • Use templates only when you need a guaranteed remount per visit: animations, per-view logging, form resets
  • Root layout.tsx is required and must include <html> and <body>

Quick example

tsx
// app/layout.tsx - state survives page transitions 'use client'; import { useState } from 'react'; export default function Layout({ children }: { children: React.ReactNode }) { const [count, setCount] = useState(0); return ( <div> <button onClick={() => setCount(c => c + 1)}>Count: {count}</button> {/* /a -> /b: count stays at whatever value it was */} {children} </div> ); } // app/template.tsx - remounts on every page transition export default function Template({ children }: { children: React.ReactNode }) { // /a -> /b: full remount, count resets to 0 return <div>{children}</div>; }

Switch from /a to /b: the layout keeps the counter value. The template resets to zero.

Key difference

Layouts render once per route segment and compose with children without remounting. React treats the layout as a stable node in the component tree, so useState, useEffect, and DOM nodes all survive client-side transitions. Templates get full teardown and remount on every route change. React's reconciler sees them as new instances each time, which re-triggers useEffect calls and resets all local state.

When to use

  • Shared navbar or sidebar with state (open menus, active tab) → layout
  • Per-visit page view logging (useEffect needs to fire every time) → template
  • Enter/exit animations that depend on mount lifecycle → template
  • Nested segments with different context (/dashboard vs /settings) → nested layout
  • Auth provider wrapping the whole app → layout (session survives transitions)
  • Root <html>/<body> wrapper → layout (required by Next.js)

Comparison table

FeatureLayoutTemplate
Remounts on transitionNoYes, every time
useStatePreservedReset
useEffectDoes not rerunRuns again
DOMNot recreatedRecreated
NestingComposes up the treeSupported, resets on each transition
Typical useNavbars, sidebars, providersAnimations, per-view logging

Nesting order inside a segment

Next.js wraps special files in a fixed order within each segment:

layout.tsx template.tsx error.tsx (React Error Boundary) loading.tsx (React Suspense) not-found.tsx page.tsx

This order matters when you combine template.tsx and loading.tsx in the same segment.

How it works internally

Next.js scans the app/ directory at build and dev time, treating layout.tsx and template.tsx as special RSC boundaries. On a route change, React's reconciler checks whether the layout component for the current segment is the same instance. It is, so no unmount happens. For templates, the reconciler sees a new instance on every transition and does a full VNode teardown and remount. Layouts stream to the browser first, painting the app shell; page content fills in after.

Common mistakes

Auth state in a template:

tsx
// app/template.tsx - WRONG 'use client'; const [user] = useSession(); // Resets on every transition, causes login loops

Templates remount on every route change, killing hook state. Session logic belongs in a layout.

Missing {children} in a layout:

tsx
// app/docs/layout.tsx - WRONG export default function DocsLayout() { return <div>Docs header</div>; // Page content disappears }

Every layout must render {children}. Without it, child pages simply do not appear.

Template for the navbar. The navbar will flicker and re-render on every link click. This is the most common App Router mistake I see in real codebases. Layouts exist precisely to prevent it.

Root layout without <html> and <body>:

tsx
// app/layout.tsx - WRONG export default function RootLayout({ children }) { return <div>{children}</div>; // Hydration fails }

Next.js requires <html> and <body> in the root layout for SSR and streaming to work.

Real-world usage

  • Vercel Dashboard: root + nested layouts keep sidebar and org-switcher state alive across transitions
  • Supabase + Next.js starters: layout wraps the auth provider so the session survives page changes
  • T3 Stack (tRPC/Next): nested layouts carry user and team context down the tree
  • Shadcn/ui templates: app shell in layout, rare onboarding screens in template
  • Any dashboard with tabs: layout holds useState('analytics') that persists from /dashboard/analytics to /dashboard/reports

Follow-up questions

Q: How do nested layouts compose? What does the tree look like for app/blog/layout.tsx and app/blog/posts/layout.tsx?
A: Root layout wraps blog layout, which wraps posts layout. All three render once per load; only the innermost page component swaps on transition. No parent remounts.

Q: Why use template.tsx instead of loading.tsx for custom reset UI?
A: loading.tsx wraps the segment in a Suspense boundary and shows a spinner during data fetching. template.tsx handles full remount logic: state reset, animations, per-view effects. Different jobs.

Q: When would you use a template for a dynamic product page?
A: If you need fresh data on every visit without caching, a template inside a dynamic route (/shop/[id]/template.tsx) with cache: 'no-store' guarantees a refetch every time the user opens that product.

Q: (Senior) How does layout streaming work with Suspense boundaries in children?
A: The layout streams to the browser first as the HTML shell. Children with async fetches suspend independently. The browser paints the shell instantly; content fills in as each Suspense boundary resolves. No waterfall, because the layout does not block child streaming.

Examples

tsx
// app/layout.tsx export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html lang="en"> <body> <Header /> <main>{children}</main> <Footer /> </body> </html> ); } // Header and Footer mount once; only <main> content swaps on page transitions

Header and Footer mount once for the lifetime of the app. Switching between any pages does not recreate them or reset their internal state.

Intermediate: Dashboard with persistent sidebar state

tsx
// app/dashboard/layout.tsx 'use client'; import { useState } from 'react'; import Sidebar from '@/components/sidebar'; export default function DashboardLayout({ children, }: { children: React.ReactNode; }) { const [activeTab, setActiveTab] = useState('analytics'); // /dashboard/analytics -> /dashboard/reports: // activeTab stays 'analytics', Sidebar does not flicker return ( <div className="flex"> <Sidebar active={activeTab} onChange={setActiveTab} /> <main className="flex-1 p-8">{children}</main> </div> ); }

activeTab survives transitions because the layout never unmounts. The page content inside <main> swaps without touching the sidebar.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?