File-Based Routing Deep Dive
Next.js uses the file system as the source of truth for routes. Understanding the conventions unlocks powerful routing patterns.
Basic Routes
Folders define route segments. A page.tsx inside makes it accessible:
app/
├── page.tsx → /
├── about/
│ └── page.tsx → /about
├── blog/
│ └── page.tsx → /blog
└── contact/
└── page.tsx → /contact
Dynamic Routes
Use square brackets for dynamic segments:
app/
├── blog/
│ └── [slug]/
│ └── page.tsx → /blog/my-first-post
├── users/
│ └── [id]/
│ └── page.tsx → /users/123
Access parameters in your component:
// app/blog/[slug]/page.tsx
export default async function BlogPost({
params,
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params
return <h1>Post: {slug}</h1>
}
Catch-All Routes
Use [...slug] to catch multiple segments:
app/
└── docs/
└── [...slug]/
└── page.tsx → /docs/a, /docs/a/b, /docs/a/b/c
// params.slug = ['a', 'b', 'c'] for /docs/a/b/c
export default async function Docs({
params,
}: {
params: Promise<{ slug: string[] }>
}) {
const { slug } = await params
return <div>Path: {slug.join('/')}</div>
}
Use [[...slug]] for optional catch-all (also matches the root):
app/
└── shop/
└── [[...categories]]/
└── page.tsx → /shop, /shop/clothes, /shop/clothes/shirts
Static Params Generation
For dynamic routes, tell Next.js which pages to pre-generate:
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
const posts = await getPosts()
return posts.map((post) => ({
slug: post.slug,
}))
}
Colocation
You can colocate files that aren't special (page, layout, etc.):
app/
└── dashboard/
├── page.tsx # The route
├── Dashboard.tsx # Component (not a route)
├── dashboard.css # Styles
└── utils.ts # Helpers
Only page.tsx, layout.tsx, and other special files affect routing.
Private Folders
Prefix with underscore to exclude from routing:
app/
├── _components/ # Shared components (not routes)
│ └── Button.tsx
├── _lib/ # Utilities (not routes)
│ └── api.ts
└── dashboard/
└── page.tsx # Actual route
Route Priority
When routes could conflict, Next.js uses this priority:
- Static routes (
/about) - Dynamic routes (
/[slug]) - Catch-all routes (
/[...slug])
app/
├── blog/
│ ├── page.tsx # /blog (static, wins)
│ ├── latest/
│ │ └── page.tsx # /blog/latest (static, wins)
│ └── [slug]/
│ └── page.tsx # /blog/anything-else (dynamic)
Summary
- Folders define routes,
page.tsxmakes them accessible [param]for single dynamic segments[...param]for catch-all,[[...param]]for optional catch-allgenerateStaticParamspre-generates dynamic routes- Underscore prefix (
_folder) excludes from routing - Static routes take priority over dynamic ones

