Layouts vs Templates
Layouts persist and preserve state across navigation; templates remount on every navigation — knowing which you need matters.
Layouts and templates look almost identical — both wrap children — but they behave in opposite ways on navigation, and picking the wrong one causes subtle bugs.
The core difference
| layout.tsx | template.tsx | |
|---|---|---|
| On navigation | persists (stays mounted) | remounts (fresh instance) |
| State | preserved | reset |
| Effects | run once | re-run each nav |
| DOM | reused | recreated |
A layout is the default and the right choice ~90% of the time: shared nav, sidebars, and providers should keep their state as users move between sibling pages.
When you actually want a template
- Enter/exit animations that must replay on every navigation (a fresh mount restarts the animation).
- Per-page
useEffectthat must fire on each visit — analytics pageviews, focus management. - Resetting
useStateor akey-sensitive feature between sibling routes.
If both files exist in a segment, the template renders inside the layout and around the page.
Example
// app/blog/template.tsx
// Remounts on every navigation between posts, so the
// fade-in animation replays each time — a layout would
// keep the DOM and the animation would only run once.
'use client';
import { motion } from 'framer-motion';
export default function BlogTemplate({
children,
}: {
children: React.ReactNode;
}) {
return (
<motion.div
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.25 }}
>
{children}
</motion.div>
);
}When to use it
- A sidebar layout uses layout.tsx so it stays mounted and scroll position is preserved when navigating between sub-pages.
- A page-level animation uses template.tsx so the enter transition fires on every navigation to that route.
- A form wizard uses template.tsx to reset form state on each step navigation instead of persisting it from the layout.
More examples
Layout stays mounted on nav
State in a layout persists across child page navigations because the layout component is never unmounted.
// app/dashboard/layout.tsx
'use client';
import { useState } from 'react';
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
const [notifications, setNotifications] = useState(0);
return (
<div>
<p>Notifications: {notifications}</p>
{children}
</div>
);
}Template remounts on every nav
template.tsx creates a new instance on every navigation, so useEffect and state always reset.
// app/dashboard/template.tsx
'use client';
import { useEffect } from 'react';
export default function DashboardTemplate({ children }: { children: React.ReactNode }) {
useEffect(() => {
console.log('Template mounted — fires on every navigation');
}, []);
return <>{children}</>;
}Template for CSS enter animation
Because templates remount, the fade-in animation runs on every page transition in the marketing group.
// app/(marketing)/template.tsx
export default function MarketingTemplate({ children }: { children: React.ReactNode }) {
return (
<div
style={{ animation: 'fadeIn 0.3s ease-in-out' }}
>
{children}
</div>
);
}
Discussion