How to Structure Layouts in Next.js App Router Using Folder Conventions
Next.js App Router uses file-system based routing where layout.tsx files placed in specific folders automatically wrap child pages and nested layouts, creating a composable UI hierarchy that persists state across navigation.
The introduction of the App Router in Next.js 13+ fundamentally changed how React applications organize UI components. By co-locating layout files with their routes, the framework eliminates manual composition boilerplate while enabling sophisticated patterns like nested persistence and parallel routing. This guide examines the implementation details from the vercel/next.js repository to explain how folder-based layout discovery and slot-based composition work under the hood.
Root Layout Requirements and Structure
Every Next.js application using the App Router must define a root layout at app/layout.tsx (or .js). This file serves as the HTML document shell and global UI wrapper for your entire application.
According to packages/next/src/lib/verify-root-layout.ts, the framework explicitly validates the presence of this file during the build process. If the root layout is missing, Next.js throws a development error preventing the application from starting.
// app/layout.tsx
import '@/styles/globals.css';
import { Inter } from 'next/font/google';
const inter = Inter({ subsets: ['latin'] });
export const metadata = {
title: 'My Next.js App',
description: 'A demo of nested layouts',
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en" className={inter.className}>
<head />
<body>
<nav>
{/* global navigation */}
</nav>
{children} {/* renders the rest of the tree */}
</body>
</html>
);
}
The children prop acts as a slot where Next.js injects the nested layout or page component. This mechanism, implemented in packages/next/src/client/components/layout-router.tsx, ensures that layout state persists across navigation while only re-rendering changed segments.
Nested Layouts for Route Segments
Nested layouts allow you to create UI shells specific to sections of your application. By placing a layout.tsx file inside any subfolder of app/, you create a layout that wraps only the routes within that directory.
When a request matches a route segment, Next.js walks up the folder tree and renders every layout it finds, stitching them together via the slot mechanism. This enables complex UI hierarchies without prop drilling or manual component composition.
// app/dashboard/layout.tsx
export const metadata = {
title: 'Dashboard – My Next.js App',
};
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<section className="dashboard">
<aside>
{/* sidebar navigation for the dashboard */}
</aside>
<main>{children}</main>
</section>
);
}
Any page inside app/dashboard/ automatically receives this layout wrapper. Deeper nesting works identically—app/dashboard/settings/layout.tsx would wrap only the settings pages, inserting its children slot into the Dashboard layout's children slot.
Parallel Routes and Layout Composition
Parallel routes allow you to render multiple UI trees simultaneously within the same layout. This pattern is useful for creating complex interfaces like dashboard sidebars, modals, or split-pane views that persist state independently.
To create a parallel route, use a folder prefix with parentheses, such as app/(modal)/. The layout inside this folder can expose multiple slots using the @folder convention, allowing the framework to render parallel segments alongside the main content.
// app/(modal)/layout.tsx
export const dynamic = 'force-static';
export default function ModalLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="modal-backdrop">
<div className="modal">{children}</div>
</div>
);
}
When navigating to a parallel route using router.push('/(modal)/settings'), Next.js renders this layout alongside the current page layout, maintaining the state of both UI trees. The layout-router.tsx component coordinates these parallel segments, ensuring they hydrate independently while preserving their respective states.
Loading and Error Boundaries for Layouts
Next.js automatically handles loading states and errors for layouts by co-locating loading.tsx and error.tsx files next to your layout.tsx. These files create React Suspense boundaries and error boundaries that wrap the layout's children.
When a layout or its child pages are fetching data, the loading.tsx component renders automatically. If an error occurs during rendering or data fetching, the error.tsx boundary catches it and displays a fallback UI.
// app/dashboard/loading.tsx
export default function DashboardLoading() {
return <p>Loading dashboard…</p>;
}
// app/dashboard/error.tsx
'use client';
export default function DashboardError({
error,
reset,
}: {
error: Error;
reset: () => void;
}) {
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={reset}>Try again</button>
</div>
);
}
The verify-root-layout.ts utility ensures these boundary files are correctly associated with their respective layouts, while layout-router.tsx manages the Suspense transitions between loading states and the final rendered content.
Summary
- Root layout requirement: Every App Router application must include
app/layout.tsxat the project root, which serves as the HTML document shell and global UI wrapper. - Folder-based discovery: Next.js automatically discovers layouts by searching for
layout.tsxfiles within theapp/directory hierarchy, wrapping all child routes found in that folder. - Slot-based composition: The
childrenprop in each layout acts as a slot where Next.js injects nested layouts or pages, implemented inpackages/next/src/client/components/layout-router.tsx. - Parallel routes: Use parentheses in folder names (e.g.,
app/(modal)/) to create parallel UI trees that render alongside the main content without changing the URL structure. - Loading and error boundaries: Co-locate
loading.tsxanderror.tsxfiles next to layouts to automatically handle Suspense and error states for that segment.
Frequently Asked Questions
How does Next.js handle nested layouts without prop drilling?
Next.js uses a file-system based routing convention where each layout.tsx file automatically wraps the pages and layouts in its directory. The framework passes the child component through the children prop, which acts as a slot. When rendering, Next.js stitches these layouts together hierarchically, with each layout receiving the next level of the tree as its children. This eliminates the need for manual component composition or prop drilling between parent and child layouts.
Can I use multiple root layouts in the same Next.js application?
No, every App Router application must have exactly one root layout at app/layout.tsx. This file is mandatory and serves as the top-level wrapper for your entire application, defining the <html> and <body> tags. However, you can create distinct layouts for different sections by using nested layouts in subdirectories or by using route groups (folders with parentheses) to create parallel layouts that don't affect the URL structure. The verify-root-layout.ts utility enforces the single root layout requirement during the build process.
What is the difference between a layout and a template in Next.js?
While both layouts and templates wrap pages, layouts preserve state across navigation and do not remount when navigating between sibling routes. Templates, on the other hand, create a new instance for each of their children on navigation, remounting the component and resetting its state. To create a template, use template.tsx instead of layout.tsx in your route folder. Templates are useful when you need enter/exit animations or state reset between pages, while layouts are better for persistent UI like navigation bars that should maintain scroll position or input values across page changes.
How do I add metadata to a specific layout without affecting child pages?
Export a metadata object (or generateMetadata async function) from your layout.tsx file. Next.js automatically merges metadata from parent layouts with child layouts and pages, with child metadata taking precedence for conflicting properties. For example, if your root layout defines title: 'My App' and a dashboard layout defines title: 'Dashboard', pages under /dashboard will display "Dashboard – My App" (depending on your title.template configuration). This hierarchical metadata system allows you to set global defaults in the root layout while overriding specific values in nested segments without manually passing props through the component tree.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →