Next.js App Router layouts: the nesting model that changed how I build UIs
The App Router's nested layout model took me a week to fully understand, but once it clicked, I rewrote my entire routing strategy. Layouts persist across navigation — they do not remount when child routes change. This single fact enables a whole category of UIs that were awkward to build in the Pages Router.
How nesting works
app/
layout.tsx ← Root layout (wraps everything, never unmounts)
page.tsx ← Home page
dashboard/
layout.tsx ← Dashboard layout (persists while in /dashboard/*)
page.tsx ← /dashboard
analytics/
layout.tsx ← Analytics sub-layout
page.tsx ← /dashboard/analytics
settings/
page.tsx ← /dashboard/settings
Navigating from /dashboard/analytics to /dashboard/settings does NOT remount the dashboard layout. Only the page component changes.
Authenticated dashboard with sidebar
// app/dashboard/layout.tsx
import { redirect } from 'next/navigation';
import { getSession } from '@/lib/auth';
export default async function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
// Auth check runs ONCE for all dashboard routes
const session = await getSession();
if (!session) redirect('/login');
return (
{/* Sidebar persists — no remount between dashboard routes */}
{children}
);
}
Keeping sidebar state through navigation
Because the layout does not remount, state in layout components persists across navigation. This is powerful for sidebars:
'use client';
export function Sidebar({ user }: { user: User }) {
const [collapsed, setCollapsed] = useState(false);
const pathname = usePathname();
// collapsed state persists through /dashboard/analytics -> /dashboard/settings navigation
// because this component never remounts
return (
);
}
Route groups for organization without URL segments
app/
(marketing)/ ← Route group — () means no URL segment
layout.tsx ← Marketing layout (nav + footer)
page.tsx ← /
about/
page.tsx ← /about
pricing/
page.tsx ← /pricing
(app)/ ← Different layout for app
layout.tsx ← App layout (sidebar, auth)
dashboard/
page.tsx ← /dashboard
profile/
page.tsx ← /profile
// app/(marketing)/layout.tsx — marketing site layout
export default function MarketingLayout({ children }: { children: React.ReactNode }) {
return (
<>
{children}
>
);
}
// app/(app)/layout.tsx — app layout (completely different)
export default async function AppLayout({ children }: { children: React.ReactNode }) {
const session = await getSession();
if (!session) redirect('/login');
return (
{children}
);
}
Templates vs layouts
If you need to reset state on every navigation (unlike layouts which persist), use a template.tsx instead:
// app/dashboard/template.tsx
// This DOES remount on every navigation — unlike layout.tsx
// Use when you need fresh state per route visit
export default function DashboardTemplate({ children }: { children: React.ReactNode }) {
// Fresh mount means fresh state, fresh effects, fresh animations
return (
{children}
);
}
The mental model shift
In the Pages Router, I thought of pages as independent units. In the App Router, I think of the URL as selecting a position in a layout tree. Most of the UI is persistent layouts; only the innermost page component changes. This model maps better to how users actually experience navigating through an app — the chrome is stable, the content changes.