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.tsxtemplate.tsx
On navigationpersists (stays mounted)remounts (fresh instance)
Statepreservedreset
Effectsrun oncere-run each nav
DOMreusedrecreated

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 useEffect that must fire on each visit — analytics pageviews, focus management.
  • Resetting useState or a key-sensitive feature between sibling routes.

If both files exist in a segment, the template renders inside the layout and around the page.

Example

Example · typescript
// 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.

Example · ts
// 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.

Example · ts
// 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.

Example · ts
// 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

  • Be the first to comment on this lesson.