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
useStateand 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.tsxis required and must include<html>and<body>
Quick example
// 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 (
useEffectneeds to fire every time) → template - Enter/exit animations that depend on mount lifecycle → template
- Nested segments with different context (
/dashboardvs/settings) → nested layout - Auth provider wrapping the whole app → layout (session survives transitions)
- Root
<html>/<body>wrapper → layout (required by Next.js)
Comparison table
| Feature | Layout | Template |
|---|---|---|
| Remounts on transition | No | Yes, every time |
useState | Preserved | Reset |
useEffect | Does not rerun | Runs again |
| DOM | Not recreated | Recreated |
| Nesting | Composes up the tree | Supported, resets on each transition |
| Typical use | Navbars, sidebars, providers | Animations, 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.tsxThis 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:
// app/template.tsx - WRONG
'use client';
const [user] = useSession(); // Resets on every transition, causes login loopsTemplates remount on every route change, killing hook state. Session logic belongs in a layout.
Missing {children} in a layout:
// 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>:
// 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/analyticsto/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
Basic: Root layout with shared header and footer
// 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 transitionsHeader 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
// 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 readyA concise answer to help you respond confidently on this topic during an interview.