Layouts are great for persistent UI, but what if you want something to re-render on every navigation? Maybe you need to reset a form, replay an animation, or track page views. That's where templates come in. Templates look like layouts but behave differently: they create a new instance on every navigation, resetting all state and effects. Understanding this subtle but crucial difference will help you choose the right tool for each scenario.
The Key Difference
layout.tsx
- Persists across page navigations
- State is maintained
- DOM elements stay mounted
- useEffect runs once
- Better for performance
- Default choice for most cases
template.tsx
- Re-renders on every navigation
- State is reset
- DOM elements remount
- useEffect runs every time
- Slight performance cost
- Special cases only
Simple Rule of Thumb
If you want UI to persist (most cases) → use layout.tsx
If you want UI to reset (rare cases) → use template.tsx
Visual Comparison: Behavior Difference
Let's see the difference with a counter example:
With layout.tsx (Persists)
'use client';
import { useState } from 'react';
import Link from 'next/link';
export default function PersistentLayout({
children,
}: {
children: React.ReactNode;
}) {
// This state PERSISTS when navigating between pages
const [count, setCount] = useState(0);
return (
<div className="p-8">
<div className="mb-6 bg-blue-50 border-2 border-blue-500 p-4 rounded">
<h2 className="font-bold mb-2">Layout (Persists)</h2>
<p className="mb-2">Count: {count}</p>
<button
onClick={() => setCount(count + 1)}
className="px-4 py-2 bg-blue-600 text-white rounded"
>
Increment
</button>
<p className="text-sm text-gray-600 mt-2">
↑ This count stays the same when you navigate
</p>
</div>
<nav className="flex gap-4 mb-6">
<Link href="/with-layout/page-1" className="text-blue-600">
Page 1
</Link>
<Link href="/with-layout/page-2" className="text-blue-600">
Page 2
</Link>
</nav>
{children}
</div>
);
}Behavior: Click increment, then navigate between pages. The count stays the same because the layout persists.
With template.tsx (Re-renders)
'use client';
import { useState } from 'react';
import Link from 'next/link';
export default function ResetTemplate({
children,
}: {
children: React.ReactNode;
}) {
// This state RESETS when navigating between pages
const [count, setCount] = useState(0);
return (
<div className="p-8">
<div className="mb-6 bg-purple-50 border-2 border-purple-500 p-4 rounded">
<h2 className="font-bold mb-2">Template (Re-renders)</h2>
<p className="mb-2">Count: {count}</p>
<button
onClick={() => setCount(count + 1)}
className="px-4 py-2 bg-purple-600 text-white rounded"
>
Increment
</button>
<p className="text-sm text-gray-600 mt-2">
↑ This count resets to 0 when you navigate
</p>
</div>
<nav className="flex gap-4 mb-6">
<Link href="/with-template/page-1" className="text-blue-600">
Page 1
</Link>
<Link href="/with-template/page-2" className="text-blue-600">
Page 2
</Link>
</nav>
{children}
</div>
);
}Behavior: Click increment, then navigate between pages. The count resets to 0 because the template re-renders.
Layouts vs Templates File Structure
See the difference in file names and behavior
Select a file or folder to see details
When to Use Templates
Templates are useful in specific scenarios:
1. Page View Analytics
Track every page view with useEffect that runs on each navigation:
'use client';
import { useEffect } from 'react';
import { usePathname } from 'next/navigation';
export default function AnalyticsTemplate({
children,
}: {
children: React.ReactNode;
}) {
const pathname = usePathname();
useEffect(() => {
// This runs on EVERY page navigation
analytics.track('page_view', { path: pathname });
console.log('Page viewed:', pathname);
}, [pathname]);
return <>{children}</>;
}2. Enter/Exit Animations
Animations that should replay on each page:
'use client';
import { motion } from 'framer-motion';
export default function AnimatedTemplate({
children,
}: {
children: React.ReactNode;
}) {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3 }}
>
{children}
</motion.div>
);
}3. Resetting Forms or State
Forms that should clear when navigating away:
'use client';
import { useState } from 'react';
export default function SearchTemplate({
children,
}: {
children: React.ReactNode;
}) {
// This resets when navigating to a different search category
const [query, setQuery] = useState('');
return (
<div>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
className="w-full px-4 py-2 border rounded mb-4"
/>
{children}
</div>
);
}4. Focus Management
Automatically focus an input on page load:
'use client';
import { useEffect, useRef } from 'react';
export default function FocusTemplate({
children,
}: {
children: React.ReactNode;
}) {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
// Focus management on each navigation
ref.current?.focus();
}, []);
return (
<div ref={ref} tabIndex={-1}>
{children}
</div>
);
}Use Templates Sparingly
Templates have a performance cost since they re-render on every navigation. Only use them when you specifically need the re-rendering behavior. Layouts are the default choice for most cases.
Using Layouts and Templates Together
You can use both in the same folder. The rendering order is:
<Layout>
<Template>
{children}
</Template>
</Layout>The layout wraps the template, which wraps the page content.
Example: Persistent Sidebar + Animated Content
// This persists across navigation
export default function BlogLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="flex">
{/* Sidebar stays mounted */}
<aside className="w-64 p-6">
<h3>Categories</h3>
{/* ... sidebar content ... */}
</aside>
{/* Content area (includes template) */}
<main className="flex-1">
{children}
</main>
</div>
);
}'use client';
import { motion } from 'framer-motion';
// This re-renders on every navigation
export default function BlogTemplate({
children,
}: {
children: React.ReactNode;
}) {
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.3 }}
>
{children}
</motion.div>
);
}Result: The sidebar persists (no re-render), but the content area fades in on each navigation!
Lifecycle Comparison
Layout Lifecycle
'use client';
import { useEffect, useState } from 'react';
export default function Layout({ children }) {
const [mounted, setMounted] = useState(false);
useEffect(() => {
console.log('Layout mounted');
setMounted(true);
return () => {
console.log('Layout unmounted');
};
}, []); // Runs ONCE when first rendered
console.log('Layout render');
return <div>{children}</div>;
}
// Navigation between pages under this layout:
// First visit: "Layout render" → "Layout mounted"
// Navigate to sibling: "Layout render" (no unmount!)
// Navigate away: "Layout unmounted"Template Lifecycle
'use client';
import { useEffect, useState } from 'react';
export default function Template({ children }) {
const [mounted, setMounted] = useState(false);
useEffect(() => {
console.log('Template mounted');
setMounted(true);
return () => {
console.log('Template unmounted');
};
}, []); // Runs on EVERY navigation
console.log('Template render');
return <div>{children}</div>;
}
// Navigation between pages:
// First visit: "Template render" → "Template mounted"
// Navigate to sibling:
// "Template unmounted" → "Template render" → "Template mounted"
// Every navigation triggers full lifecycle!Practical Examples
Example 1: Multi-Step Form
Use layout for persistent progress, template for resetting each step:
'use client';
import { useState } from 'react';
// Layout: Persistent progress tracker
export default function CheckoutLayout({ children }) {
const [completedSteps, setCompletedSteps] = useState<string[]>([]);
return (
<div>
{/* Progress bar persists */}
<div className="mb-6">
<div className="flex gap-4">
{['Info', 'Shipping', 'Payment'].map((step) => (
<div
key={step}
className={`flex-1 p-3 rounded ${
completedSteps.includes(step)
? 'bg-green-100'
: 'bg-gray-100'
}`}
>
{step}
</div>
))}
</div>
</div>
{children}
</div>
);
}'use client';
// Template: Each step's form resets
export default function CheckoutTemplate({ children }) {
// Form state resets when navigating to next step
return (
<div className="animate-fadeIn">
{children}
</div>
);
}Example 2: Dashboard with Live Updates
'use client';
import { useEffect, useState } from 'react';
// Layout: Persistent WebSocket connection
export default function DashboardLayout({ children }) {
const [notifications, setNotifications] = useState<string[]>([]);
useEffect(() => {
// WebSocket stays connected
const ws = new WebSocket('wss://api.example.com');
ws.onmessage = (event) => {
setNotifications(prev => [...prev, event.data]);
};
return () => ws.close();
}, []); // Only runs once
return (
<div>
{/* Notification bell persists */}
<header>
<div>🔔 {notifications.length}</div>
</header>
{children}
</div>
);
}'use client';
import { useEffect } from 'react';
// Template: Track page views
export default function DashboardTemplate({ children }) {
useEffect(() => {
// Runs on every page navigation
trackPageView();
}, []);
return <>{children}</>;
}Best Practices
1. Default to Layouts
Use layout.tsx as your default. Only switch to template.tsx when you specifically need re-rendering behavior.
2. Document Template Usage
// app/section/template.tsx
/**
* TEMPLATE: Used instead of layout because:
* - Need to track page views on every navigation
* - Form state should reset between pages
* - Animations should replay on each page
*/
export default function Template({ children }) {
// ...
}3. Combine Strategically
// ✅ Good: Layout for persistent UI, template for animations
app/blog/
layout.tsx ← Sidebar (persists)
template.tsx ← Fade animation (re-renders)
// ❌ Unnecessary: Both doing same thing
app/section/
layout.tsx ← Just structure
template.tsx ← Also just structure (redundant)4. Consider Performance
// ❌ Bad: Expensive operations in template
export default function Template({ children }) {
const data = expensiveCalculation(); // Runs on EVERY nav
return <div>{children}</div>;
}
// ✅ Good: Move expensive ops to layout
export default function Layout({ children }) {
const data = expensiveCalculation(); // Runs ONCE
return <div>{children}</div>;
}5. Be Explicit About State Reset
// Add clear comments when using templates
export default function SearchTemplate({ children }) {
// State resets on navigation - this is intentional
// User's search should clear when switching categories
const [query, setQuery] = useState('');
return <>{children}</>;
}Common Mistakes
Mistake 1: Using Template When Layout Would Work
// ❌ Bad: No reason for template here
// app/blog/template.tsx
export default function Template({ children }) {
return (
<div className="container">
<aside>Sidebar</aside>
<main>{children}</main>
</div>
);
}
// ✅ Good: Just use layout
// app/blog/layout.tsx
export default function Layout({ children }) {
return (
<div className="container">
<aside>Sidebar</aside>
<main>{children}</main>
</div>
);
}Mistake 2: Expecting State to Persist in Templates
// ❌ Won't work: State resets on navigation
// app/template.tsx
export default function Template({ children }) {
const [user, setUser] = useState(null);
// User will be lost on every navigation!
return <>{children}</>;
}
// ✅ Use layout for persistent state
// app/layout.tsx
export default function Layout({ children }) {
const [user, setUser] = useState(null);
// User persists across navigation
return <>{children}</>;
}Mistake 3: Not Understanding the Performance Impact
Templates re-render on every navigation, which means re-running all component logic, effects, and re-mounting DOM elements. This has a performance cost that's usually unnecessary.
Decision Flowchart
State persists, better performance, default choice
(animations, analytics, state reset)
Re-renders on every navigation
Default to layouts
Interactive Example
Template Example with Render Counting
See how templates re-render on every navigation
Output Preview
Key Takeaways
- Layouts persist - state maintained across navigation
- Templates re-render - state resets on every navigation
- Default to layouts - use templates only when needed
- Templates have performance cost - re-mounting is expensive
- Can use both together - Layout wraps Template wraps Children
- Templates for: animations, analytics, state reset
- Layouts for: persistent UI, shared state, performance
- Document template usage - explain why it's needed
What's Next?
You now understand the subtle but important difference between layouts and templates! Next, we'll explore loading states with loading.tsx files. You'll learn how to create instant loading UI, use React Suspense boundaries automatically, and build skeleton screens that make your app feel incredibly fast.
Loading states are crucial for good UX—they show users that something is happening and prevent jarring blank screens. Let's master them!
🎯 When in Doubt
If you're unsure whether to use a layout or template, choose layout. It's the right choice 95% of the time. Only use templates when you have a specific, documented reason for needing re-rendering behavior.