Skip to main content

Overview

Next.js App Router provides two powerful features for creating consistent UI patterns: layouts and templates. Understanding the difference between them is crucial for building maintainable applications.

Layouts

Layouts are shared UI that persists across route changes and maintains state during navigation.

Layout Characteristics

  • Shared UI across multiple pages
  • Maintains state during navigation
  • Renders once and stays mounted
  • Perfect for navigation, headers, footers

Layout Benefits

  • Consistent user experience
  • Performance optimization
  • State preservation
  • Reduced re-rendering

Templates

Templates create a new instance for each route and re-render on every navigation.

Template Characteristics

  • Creates new instance for each route
  • Re-renders on every navigation
  • Perfect for animations and transitions
  • Useful for enter/exit effects

Template Benefits

  • Page transition animations
  • Fresh state for each route
  • Custom enter/exit effects
  • Route-specific styling

Root Layout

The root layout wraps all pages and is required in every Next.js app.
// app/layout.tsx
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "My App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en" className={inter.className}>
      <body>
        <header>
          <nav>
            <a href="/">Home</a>
            <a href="/about">About</a>
            <a href="/contact">Contact</a>
          </nav>
        </header>
        <main>{children}</main>
        <footer>
          <p>&copy; 2024 My App. All rights reserved.</p>
        </footer>
      </body>
    </html>
  );
}

Nested Layouts

Layouts can be nested to create hierarchical UI structures.
// app/dashboard/layout.tsx
import Sidebar from "@/components/Sidebar";

export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="dashboard-layout">
      <Sidebar />
      <div className="dashboard-content">{children}</div>
    </div>
  );
}

Layout Hierarchy

app/
├── layout.tsx              # Root layout (wraps everything)
├── dashboard/
│   ├── layout.tsx          # Dashboard layout (wraps dashboard pages)
│   ├── page.tsx            # /dashboard
│   └── settings/
│       ├── layout.tsx      # Settings layout (wraps settings pages)
│       └── page.tsx        # /dashboard/settings

Basic Template

// app/template.tsx
"use client";

import { useEffect, useState } from "react";
import { usePathname } from "next/navigation";

export default function Template({ children }: { children: React.ReactNode }) {
  const [isLoading, setIsLoading] = useState(false);
  const pathname = usePathname();

  useEffect(() => {
    setIsLoading(true);
    const timer = setTimeout(() => setIsLoading(false), 300);
    return () => clearTimeout(timer);
  }, [pathname]);

  return (
    <div
      className={`transition-opacity duration-300 ${
        isLoading ? "opacity-0" : "opacity-100"
      }`}>
      {children}
    </div>
  );
}

Animated Template

// app/template.tsx
"use client";

import { motion } from "framer-motion";
import { usePathname } from "next/navigation";

export default function AnimatedTemplate({
  children,
}: {
  children: React.ReactNode;
}) {
  const pathname = usePathname();

  return (
    <motion.div
      key={pathname}
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      exit={{ opacity: 0, y: -20 }}
      transition={{ duration: 0.3 }}>
      {children}
    </motion.div>
  );
}

Authentication Layout

// app/(auth)/layout.tsx
import Image from "next/image";
import logo from "@/public/logo.png";

export default function AuthLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="min-h-screen flex items-center justify-center bg-gray-50">
      <div className="max-w-md w-full space-y-8">
        <div className="text-center">
          <Image
            src={logo}
            alt="Logo"
            width={80}
            height={80}
            className="mx-auto"
          />
          <h2 className="mt-6 text-3xl font-extrabold text-gray-900">
            Welcome Back
          </h2>
        </div>

        <div className="bg-white py-8 px-6 shadow rounded-lg">{children}</div>
      </div>
    </div>
  );
}

Admin Layout

// app/admin/layout.tsx
import AdminSidebar from "@/components/AdminSidebar";
import AdminHeader from "@/components/AdminHeader";

export default function AdminLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="admin-layout">
      <AdminSidebar />
      <div className="admin-main">
        <AdminHeader />
        <div className="admin-content">{children}</div>
      </div>
    </div>
  );
}

E-commerce Layout

// app/shop/layout.tsx
import ShopHeader from "@/components/ShopHeader";
import ShopFooter from "@/components/ShopFooter";
import CartSidebar from "@/components/CartSidebar";

export default function ShopLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="shop-layout">
      <ShopHeader />
      <main className="shop-main">{children}</main>
      <ShopFooter />
      <CartSidebar />
    </div>
  );
}

Loading Templates

// app/loading.tsx
export default function Loading() {
  return (
    <div className="flex items-center justify-center min-h-screen">
      <div className="animate-spin rounded-full h-32 w-32 border-b-2 border-blue-500"></div>
    </div>
  );
}

Error Boundaries

// app/error.tsx
"use client";

import { useEffect } from "react";

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  useEffect(() => {
    console.error(error);
  }, [error]);

  return (
    <div className="error-boundary">
      <h2>Something went wrong!</h2>
      <button onClick={reset}>Try again</button>
    </div>
  );
}

Not Found Pages

// app/not-found.tsx
import Link from "next/link";

export default function NotFound() {
  return (
    <div className="not-found">
      <h2>Not Found</h2>
      <p>Could not find requested resource</p>
      <Link href="/">Return Home</Link>
    </div>
  );
}

Conditional Layouts

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

import { usePathname } from "next/navigation";
import AuthLayout from "@/components/AuthLayout";
import MainLayout from "@/components/MainLayout";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const pathname = usePathname();
  const isAuthPage = pathname.startsWith("/auth");

  if (isAuthPage) {
    return <AuthLayout>{children}</AuthLayout>;
  }

  return <MainLayout>{children}</MainLayout>;
}

Dynamic Layouts

// app/layout.tsx
import { getLayout } from "@/lib/layouts";

export default async function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const Layout = await getLayout();

  return <Layout>{children}</Layout>;
}

Layout with Context

// app/dashboard/layout.tsx
"use client";

import { createContext, useContext, useState } from "react";

const DashboardContext = createContext({
  sidebarOpen: false,
  setSidebarOpen: (open: boolean) => {},
});

export const useDashboard = () => useContext(DashboardContext);

export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const [sidebarOpen, setSidebarOpen] = useState(false);

  return (
    <DashboardContext.Provider value={{ sidebarOpen, setSidebarOpen }}>
      <div className="dashboard-layout">
        <Sidebar />
        <main className="dashboard-main">{children}</main>
      </div>
    </DashboardContext.Provider>
  );
}

Layout Design

1

Keep Layouts Simple

Focus on shared UI elements and avoid complex logic
2

Use Nested Layouts

Create hierarchical layouts for better organization
3

Optimize Performance

Minimize re-renders and use proper state management
4

Handle Loading States

Provide appropriate loading and error states

Template Usage

1

Use for Animations

Templates are perfect for page transition animations
2

Fresh State

Use when you need fresh state for each route
3

Enter/Exit Effects

Implement custom enter and exit animations
4

Performance Consideration

Be mindful of re-rendering costs

Responsive Layouts

// app/layout.tsx
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <div className="min-h-screen flex flex-col">
          <header className="bg-white shadow-sm">
            <nav className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
              {/* Navigation content */}
            </nav>
          </header>

          <main className="flex-1">{children}</main>

          <footer className="bg-gray-800 text-white">
            <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
              {/* Footer content */}
            </div>
          </footer>
        </div>
      </body>
    </html>
  );
}

Layout with Metadata

// app/blog/layout.tsx
import { Metadata } from "next";

export const metadata: Metadata = {
  title: "Blog - My App",
  description: "Read our latest blog posts",
};

export default function BlogLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="blog-layout">
      <aside className="blog-sidebar">
        <h2>Categories</h2>
        {/* Category links */}
      </aside>
      <main className="blog-main">{children}</main>
    </div>
  );
}
Problem: Layout styles not showing on child routes Solution: Check that layout.tsx is in the correct directory and wraps children properly
Problem: State is lost when navigating between routes Solution: Ensure you’re using layouts (not templates) for state that should persist
Problem: Template is causing performance issues Solution: Consider using layouts instead, or optimize the template logic
Problem: Nested layouts are conflicting with each other Solution: Review the layout hierarchy and ensure proper nesting
Key Takeaway: Layouts and templates are powerful tools for creating consistent UI patterns. Use layouts for shared UI that should persist across routes, and templates for animations and fresh state on each navigation.