Understanding File-Based Routing
Next.js uses the filesystem as its router. The folder structure inside app/ directly maps to URL segments. This makes routes immediately discoverable and eliminates route configuration files.
Special File Conventions
Every route segment can contain these special files:
| File | Purpose |
|---|---|
page.tsx |
The UI for that route — makes the segment public |
layout.tsx |
Wraps pages in that segment and below |
loading.tsx |
Streaming loading state using Suspense |
error.tsx |
Error boundary for that segment |
not-found.tsx |
404 UI for that segment |
route.ts |
API endpoint (no UI) |
Static Routes
app/
├── page.tsx → /
├── about/
│ └── page.tsx → /about
└── blog/
└── page.tsx → /blog
Dynamic Routes
Use square brackets for dynamic segments:
app/
└── blog/
├── page.tsx → /blog
└── [slug]/
└── page.tsx → /blog/any-slug
// app/blog/[slug]/page.tsx
interface Props {
params: Promise<{ slug: string }>;
}
export default async function BlogPost({ params }: Props) {
const { slug } = await params; // Next.js 15: params is a Promise
const post = await getPost(slug);
return <article>{post.title}</article>;
}
Important Next.js 15 change: params and searchParams are now Promises and must be awaited.
Catch-All and Optional Catch-All
app/
└── docs/
└── [...slug]/
└── page.tsx → /docs/a, /docs/a/b, /docs/a/b/c
└── [[...slug]]/
└── page.tsx → /docs, /docs/a, /docs/a/b (optional)
Route Groups
Parentheses create groups that do not affect the URL:
app/
├── (marketing)/
│ ├── layout.tsx # Marketing layout
│ ├── page.tsx → /
│ └── about/
│ └── page.tsx → /about
└── (dashboard)/
├── layout.tsx # Dashboard layout (sidebar, nav)
└── settings/
└── page.tsx → /settings
Route groups let different sections of your app share different layouts without affecting the URL structure.