Pages, Layouts, and Nested Routing
The idea in one sentence
Section titled “The idea in one sentence”In the App Router a folder is a route segment, page.tsx makes that segment publicly routable, and layout.tsx wraps the segment plus every child and stays mounted across navigation.
Folder = route segment
Section titled “Folder = route segment”Each folder under app/ maps to a URL segment. A segment only becomes a reachable URL when it contains a page.tsx.
app/ layout.tsx // root layout (required) page.tsx // route: / dashboard/ layout.tsx // wraps /dashboard and its children page.tsx // route: /dashboard settings/ page.tsx // route: /dashboard/settingsA page.tsx is a Server Component by default and renders the unique UI for its segment.
export default function DashboardPage() { return <h1>Dashboard</h1>}The required root layout
Section titled “The required root layout”Every App Router app needs one root layout. It is the only layout that renders the <html> and <body> tags, and it must render children.
// app/layout.tsx — the required root layoutexport default function RootLayout({ children,}: { children: React.ReactNode}) { return ( <html lang="en"> <body>{children}</body> </html> )}Nested layouts persist and compose
Section titled “Nested layouts persist and compose”A layout.tsx wraps its own segment and all descendants. Nested layouts compose from the outside in: root wraps dashboard wraps settings. Crucially a layout PERSISTS across navigation — when you move between sibling routes the shared layout does not re-render, so its state and scroll position are preserved.
// app/dashboard/layout.tsx — persists across navigationexport default function DashboardLayout({ children,}: { children: React.ReactNode}) { return ( <section> <nav>{/* stays mounted, keeps scroll + local state */}</nav> <main>{children}</main> </section> )}loading.tsx and error.tsx
Section titled “loading.tsx and error.tsx”Add loading.tsx and Next.js automatically wraps the segment in a Suspense boundary — the fallback shows instantly while the server work streams in.
// app/dashboard/loading.tsx — automatic Suspense boundaryexport default function Loading() { return <p>Loading dashboard…</p>}Add error.tsx and Next.js wraps the segment in an error boundary. It must be a Client Component and it receives error plus a reset function to retry rendering the segment.
'use client' // error boundaries must be Client Components
export default function Error({ error, reset,}: { error: Error & { digest?: string } reset: () => void}) { return ( <div> <h2>Something went wrong</h2> <button onClick={() => reset()}>Try again</button> </div> )}graph TD R["app/layout.tsx (root, persists)"] --> D["dashboard/layout.tsx (persists)"] D --> P["dashboard/page.tsx"] D --> S["settings/page.tsx"]