Next.js Layouts and Templates are how you share UI across pages without duplicating code, but the real magic is how they let you selectively re-render parts of your app during navigation.

Let’s see this in action. Imagine a simple dashboard with a persistent sidebar and header.

// app/layout.tsx
import './globals.css';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <header>This is the site header</header>
        <nav>
          <ul>
            <li><a href="/">Home</a></li>
            <li><a href="/dashboard">Dashboard</a></li>
            <li><a href="/settings">Settings</a></li>
          </ul>
        </nav>
        <main>{children}</main>
        <footer>This is the site footer</footer>
      </body>
    </html>
  );
}

Now, let’s add a template.tsx inside the app/dashboard directory.

// app/dashboard/template.tsx
'use client'; // Important for template to be interactive

import { useEffect } from 'react';

export default function DashboardTemplate({
  children,
}: {
  children: React.ReactNode;
}) {
  useEffect(() => {
    console.log('Dashboard template mounted/updated');
    // This will log on initial load and subsequent navigations *within* the dashboard
  }, []);

  return (
    <div>
      <h2>Dashboard Section</h2>
      {children}
    </div>
  );
}

And a layout.tsx for the dashboard.

// app/dashboard/layout.tsx
export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div>
      <p>This is the dashboard layout wrapper.</p>
      {children}
    </div>
  );
}

When you navigate from / to /dashboard, the RootLayout remains mounted. The DashboardLayout and DashboardTemplate are mounted. The children of DashboardTemplate will be your /app/dashboard/page.tsx content.

Now, if you navigate from /dashboard to /dashboard/settings (assuming /app/dashboard/settings/page.tsx exists), the RootLayout, DashboardLayout, and DashboardTemplate will stay mounted. Only the children of the DashboardTemplate (which is the content of /app/dashboard/settings/page.tsx) will be re-rendered. You’ll see "Dashboard template mounted/updated" log again in the console.

This distinction is key: layout.tsx preserves state and DOM across navigations, while template.tsx re-mounts its children on every navigation. This makes template.tsx ideal for animations or resetting component state when navigating to a new page within the same layout.

The core problem this solves is managing shared UI and stateful components during client-side navigation. Before layouts, you’d often end up with wrapper components that would re-render entirely, losing their state. Layouts provide a persistent shell. Templates offer a controlled way to re-render within that shell.

When you define a layout.tsx file, it wraps all child segments. For example, app/dashboard/layout.tsx wraps app/dashboard/page.tsx, app/dashboard/settings/page.tsx, and any other pages or nested layouts within the app/dashboard directory. The RootLayout (app/layout.tsx) wraps everything.

The template.tsx file, on the other hand, is instantiated for every child segment. This means app/dashboard/template.tsx will wrap app/dashboard/page.tsx, and if you had app/dashboard/settings/page.tsx, it would also be wrapped by app/dashboard/template.tsx each time you navigate to it. This is why useEffect in a template will re-run on navigation.

Think of layout.tsx as the persistent structure of a building (walls, foundation) and template.tsx as the furniture and decor within a specific room that might get rearranged or replaced when you enter.

The "template" concept is particularly useful when you want to animate page transitions. By having a template.tsx that re-mounts, you can easily apply exit and enter animations to its children using libraries like Framer Motion. The layout.tsx would remain untouched, preserving any global state like a user session or theme.

Here’s a more concrete example of how state is preserved in a layout.

// app/layout.tsx
'use client';

import { useState } from 'react';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const [count, setCount] = useState(0);

  return (
    <html lang="en">
      <body>
        <button onClick={() => setCount(count + 1)}>Increment Global Count: {count}</button>
        <nav>
          <ul>
            <li><a href="/">Home</a></li>
            <li><a href="/dashboard">Dashboard</a></li>
          </ul>
        </nav>
        <main>{children}</main>
      </body>
    </html>
  );
}

If you click the "Increment Global Count" button on the home page, then navigate to /dashboard, the count state will be preserved because RootLayout is not re-mounted. If you were to use a template.tsx at the root level instead, this state would be reset on every navigation.

The most surprising thing about layouts and templates is how they interact with React Server Components and client-side navigation simultaneously. When you navigate between pages that share a layout, Next.js intelligently fetches only the necessary new Server Components for the updated page and client-side bundles for any interactive components, while keeping the shared layout and its state intact. This isn’t just client-side routing; it’s a hybrid approach that optimizes data fetching and rendering.

The next thing you’ll likely encounter is how to manage parallel routes and intercepting routes within this layout and template structure.

Want structured learning?

Take the full Nextjs course →