Skip to content

Pages, Layouts, and Nested Routing

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.

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/settings

A page.tsx is a Server Component by default and renders the unique UI for its segment.

app/dashboard/page.tsx
export default function DashboardPage() {
return <h1>Dashboard</h1>
}

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 layout
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}

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 navigation
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<section>
<nav>{/* stays mounted, keeps scroll + local state */}</nav>
<main>{children}</main>
</section>
)
}

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 boundary
export 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"]
Nested layouts compose and persist
What makes a folder segment publicly routable?
What is special about a layout across navigation?
Which layout renders the html and body tags?
What must error.tsx be, and what does it receive?