Next.js App Router uses the app/ directory where folder structure defines URL routes — page.tsx makes a route public, layout.tsx wraps child routes with shared UI, loading.tsx and error.tsx provide automatic loading/error states, and dynamic segments like [id] capture URL parameters.
page.tsx makes a route public, layout.tsx wraps children (persists across navigation), loading.tsx creates Suspense boundary, error.tsx creates error boundary.
[id] captures single segment, [...slug] catches all segments, [[...slug]] is optional catch-all. params is a Promise in Next.js 15+.
Layouts wrap child routes and don't re-render on navigation — shared UI (sidebars, headers) stays mounted. Root layout is required with html/body.
Parenthesized folders organize without affecting URLs. @slots enable parallel rendering of multiple pages in one layout.
Next.js uses file-based routing — the file and folder structure in your project directly maps to URL routes. No router configuration needed.
The app/ directory uses special file conventions:
| File | Purpose |
|------|---------|
| page.tsx | Makes the route publicly accessible |
| layout.tsx | Shared UI wrapper for child routes |
| loading.tsx | Automatic loading UI (Suspense boundary) |
| error.tsx | Automatic error UI (error boundary) |
| not-found.tsx | Custom 404 UI |
| route.ts | API endpoint (Route Handler) |
| template.tsx | Like layout but re-mounts on navigation |
app/page.tsx → /
app/about/page.tsx → /about
app/blog/page.tsx → /blog
app/blog/[slug]/page.tsx → /blog/my-post (dynamic)
app/shop/[...slug]/page.tsx → /shop/a/b/c (catch-all)
app/dashboard/settings/page.tsx → /dashboard/settings (nested)Folders without page.tsx are not routes — they organize code without creating URLs.
Layouts wrap child routes and persist across navigation — they don't re-render when the child page changes:
// app/dashboard/layout.tsx
export default function DashboardLayout({ children }) {
return (
<div className="flex">
<Sidebar /> {/* Doesn't re-render on navigation */}
<main>{children}</main>
</div>
);
}The root layout (app/layout.tsx) is required and wraps the entire app — it must include <html> and <body> tags. Nested layouts compose automatically.
Square brackets create dynamic segments: app/blog/[slug]/page.tsx matches /blog/anything.
export default async function BlogPost({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const post = await getPost(slug);
return <article>{post.content}</article>;
}In Next.js 15+, params is a Promise that must be awaited.
Variants:
[id] — single dynamic segment (/users/123)[...slug] — catch-all segments (/docs/a/b/c → slug: ['a', 'b', 'c'])[[...slug]] — optional catch-all (also matches the parent route)For static generation of dynamic routes, export generateStaticParams to tell Next.js which pages to pre-render:
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map(post => ({ slug: post.slug }));
}Pages not listed are generated on-demand at request time (and cached for subsequent requests).
Parenthesized folders like (marketing) organize routes without affecting the URL:
app/(marketing)/about/page.tsx → /about
app/(marketing)/pricing/page.tsx → /pricing
app/(dashboard)/settings/page.tsx → /settingsRoute groups can have their own layouts — useful for sections with different designs (marketing pages vs dashboard).
Parallel routes render multiple pages simultaneously in the same layout using @ slots:
app/dashboard/@analytics/page.tsx
app/dashboard/@team/page.tsx
app/dashboard/layout.tsx → receives { analytics, team } as propsUseful for dashboards, split views, and modals.
Routes prefixed with (.), (..), or (...) intercept navigation to show a different view:
(.)photo — intercept same level(..)photo — intercept one level upCommon pattern: clicking a photo in a feed shows it in a modal (intercepted), but visiting the URL directly shows the full page.
The App Router uses file conventions: page.tsx for routes, layout.tsx for shared wrappers (persist across navigation), loading.tsx/error.tsx for automatic Suspense/error boundaries. Dynamic segments use [brackets]. generateStaticParams pre-renders dynamic routes at build time. Route groups (parentheses) organize without affecting URLs. Layouts never re-render when child pages change.
Fun Fact
The App Router's file conventions (page.tsx, layout.tsx, loading.tsx, error.tsx) were designed after studying how developers actually structure React applications. The Next.js team found that 90%+ of apps needed the same patterns — layouts that persist, loading states, and error boundaries — so they made these first-class file conventions instead of requiring manual React code.