app/
layout.tsx // root layout: html + body
page.tsx // "/"
dashboard/
layout.tsx // persists across /dashboard/*
template.tsx // remounts on each navigation
loading.tsx // Suspense fallback
error.tsx // "use client" error boundary
not-found.tsx
page.tsx // "/dashboard"
api/health/
route.ts // GET /api/health
Each special file has one job inside a route folder:
page.tsx: makes the folder a public route. Without apage, the folder has no URL.layout.tsx: wraps the page and its children, and stays mounted across navigations. The root layout must renderhtmlandbody.loading.tsx: wraps the segment in a Suspense boundary. Its UI shows instantly while the page streams in.error.tsx: an error boundary for the segment. It must be a Client Component.not-found.tsx: renders when code callsnotFound()or when a URL matches nothing.template.tsx: like a layout, but it remounts on every navigation.route.ts: defines a Route Handler, which is an HTTP endpoint.
A segment is one folder level of the URL, like blog in /blog/post. A boundary is a wrapper that catches a state (loading or error) for everything inside it, and shows a fallback UI instead.
Two rules often surprise people. First, error.tsx does not catch errors from the layout in the same segment. The parent segment's error boundary, or global-error.tsx, handles those. Second, route.ts cannot live next to a page.tsx in the same segment.
pagemakes a folder a routable URL.layoutwraps its children and stays mounted across navigations.loadingcreates an automatic Suspense boundary for the segment.errormust be a Client Component.errordoes not catch errors from the layout in the same folder.not-foundrenders when you callnotFound()or nothing matches.templateremounts on every navigation, androutedefines an HTTP endpoint.