Layouts, Templates & Shared UI Architecture
In modern web applications, large portions of the UI (such as global headers, navigation sidebars, breadcrumbs, and footers) remain constant as users navigate between pages. Next.js provides a sophisticated layout hierarchy via layout.tsx and template.tsx that preserves component state, prevents unnecessary DOM re-rendering, and supports nested view hierarchies.
In this lesson, you will master the mandatory root layout, nested layouts, layout state persistence, and understand when to choose template.tsx over layout.tsx.
┌─────────────────────────────────────────────────────────────────────────────┐
│ Next.js Layout Nesting Cascade │
├─────────────────────────────────────────────────────────────────────────────┤
│ app/layout.tsx (Root Layout: <html>, <body>, Global Providers, Nav) │
│ └── app/dashboard/layout.tsx (Dashboard Shell: Sidebar, Auth Check) │
│ └── app/dashboard/settings/layout.tsx (Settings Tab Bar) │
│ └── app/dashboard/settings/page.tsx (Active Page Content) │
└─────────────────────────────────────────────────────────────────────────────┘
1. The Mandatory Root Layout (app/layout.tsx)
Every Next.js App Router project must have a top-level app/layout.tsx. It is the only layout that contains the <html> and <body> tags:
// 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 Enterprise Platform',
description: 'Built with Next.js App Router',
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className={`${inter.className} bg-slate-900 text-slate-100 antialiased`}>
{children}
</body>
</html>
);
}
2. Nested Layouts & State Persistence
When a user navigates between sibling routes inside a folder (e.g. from /dashboard/analytics to /dashboard/settings), Next.js only re-renders the page.tsx component. The parent layout.tsx never unmounts or re-renders, preserving scroll positions, active tab states, and form inputs:
// app/dashboard/layout.tsx
import { DashboardSidebar } from '@/components/DashboardSidebar';
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="flex min-h-screen">
{/* Sidebar stays mounted across all dashboard sub-page navigations */}
<DashboardSidebar />
<main className="flex-1 p-8">{children}</main>
</div>
);
}
3. layout.tsx vs template.tsx
While layouts maintain their state and do not re-render across sub-routes, templates (template.tsx) create a new component instance on every navigation:
┌──────────────────────────────────────┬──────────────────────────────────────┐
│ layout.tsx (Default & Recommended) │ template.tsx (Opt-In Exception) │
├──────────────────────────────────────┼──────────────────────────────────────┤
│ • Persists across navigations │ • Creates a new instance every time │
│ • Maintains state & scroll position │ • State is reset upon navigation │
│ • Effects (useEffect) do not re-run │ • useEffect re-runs on every page │
│ • Best for: Navbars, Sidebars, Shells│ • Best for: Page entry animations, │
│ │ per-page analytics tracking │
└──────────────────────────────────────┴──────────────────────────────────────┘
Example template.tsx for Page Transition Animations:
// app/template.tsx
'use client';
import { motion } from 'framer-motion';
export default function Template({ children }: { children: React.ReactNode }) {
return (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.2 }}
>
{children}
</motion.div>
);
}
Summary & Key Takeaways
app/layout.tsxis required and must define the<html>and<body>tags.- Nested layouts nest automatically based on the folder hierarchy.
- Layouts persist across navigations without unmounting, guaranteeing high performance.
- Use
template.tsxonly when you specifically require component remounting (e.g. CSS entrance animations).
Best Practices & Senior Guidance
- Do Not Fetch Per-Page Data in Root Layout: Data fetched in root layout blocks initial page generation; fetch page-specific data inside individual
page.tsxcomponents. - Keep Global Providers in Root Layout: Wrap React Context Providers (Theme, QueryClient, Auth) in client component wrappers inside
app/layout.tsx.