Next.js Cheatsheet
Dynamic Routes
Use this Next.js reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
Dynamic Segment Syntax
| Folder name | URL match | params shape |
|---|---|---|
[id] | /posts/42 | { id: '42' } |
[slug] | /blog/my-post | { slug: 'my-post' } |
[...segments] | /docs/a/b/c | { segments: ['a', 'b', 'c'] } |
[[...segments]] | /docs OR /docs/a/b | { segments: [] } or { segments: ['a', 'b'] } |
Single Dynamic Segment
// app/blog/[slug]/page.tsx → /blog/anything export default async function BlogPost({ params, }: { params: Promise<{ slug: string }> }) { const { slug } = await params const post = await fetchPost(slug) return <article>{post.body}</article> }
Catch-All Segment [...slug]
Matches one or more path segments. The segment is required — /docs alone returns 404.
// app/docs/[...slug]/page.tsx → /docs/a /docs/a/b /docs/a/b/c export default async function DocsPage({ params, }: { params: Promise<{ slug: string[] }> }) { const { slug } = await params // slug = ['a'] OR ['a', 'b'] OR ['a', 'b', 'c'] const path = slug.join('/') return <div>Path: {path}</div> }
Optional Catch-All [[...slug]]
Same as catch-all, but also matches the base path with no segments.
// app/docs/[[...slug]]/page.tsx → /docs /docs/a /docs/a/b export default async function DocsPage({ params, }: { params: Promise<{ slug?: string[] }> }) { const { slug } = await params // slug = undefined OR ['a'] OR ['a', 'b'] if (!slug) return <DocsHome /> return <DocsPage path={slug.join('/')} /> }
generateStaticParams — Pre-generate at Build
// app/blog/[slug]/page.tsx export async function generateStaticParams() { const posts = await fetchAllPosts() return posts.map(post => ({ slug: post.slug, // must match the segment name })) }
Multi-segment:
// app/[lang]/[slug]/page.tsx export async function generateStaticParams() { return [ { lang: 'en', slug: 'hello' }, { lang: 'fr', slug: 'bonjour' }, ] }
Catch-all:
// app/docs/[...slug]/page.tsx export async function generateStaticParams() { return [ { slug: ['getting-started'] }, { slug: ['api', 'reference'] }, { slug: ['api', 'reference', 'hooks'] }, ] }
dynamicParams — Fallback for Unknown Params
// page.tsx or layout.tsx export const dynamicParams = true // (default) render on-demand for unknown params export const dynamicParams = false // 404 for any param not in generateStaticParams
Multiple Dynamic Segments
app/[org]/[repo]/page.tsx → /vercel/next.js
export default async function RepoPage({ params, }: { params: Promise<{ org: string; repo: string }> }) { const { org, repo } = await params return <div>{org}/{repo}</div> }
Dynamic Segments in Layouts
Layouts receive params too:
// app/blog/[slug]/layout.tsx export default async function PostLayout({ children, params, }: { children: React.ReactNode params: Promise<{ slug: string }> }) { const { slug } = await params const post = await fetchPost(slug) return ( <div> <header>{post.title}</header> {children} </div> ) }
Combining with searchParams
// app/search/page.tsx → /search?q=next&page=2 export default async function SearchPage({ searchParams, }: { searchParams: Promise<{ q?: string; page?: string }> }) { const { q = '', page = '1' } = await searchParams const results = await search(q, Number(page)) return <ResultsList results={results} /> }
Client-side Access to Dynamic Params
'use client' import { useParams, useSearchParams } from 'next/navigation' export function PostActions() { const params = useParams<{ slug: string }>() const searchParams = useSearchParams() const slug = params.slug const ref = searchParams.get('ref') return <div>{slug} — referred by {ref}</div> }
Generating Metadata Dynamically
// app/blog/[slug]/page.tsx import type { Metadata } from 'next' export async function generateMetadata({ params, }: { params: Promise<{ slug: string }> }): Promise<Metadata> { const { slug } = await params const post = await fetchPost(slug) return { title: post.title, description: post.excerpt, openGraph: { title: post.title, images: [post.ogImage], }, } }
notFound() for Missing Dynamic Resources
import { notFound } from 'next/navigation' export default async function PostPage({ params, }: { params: Promise<{ slug: string }> }) { const { slug } = await params const post = await db.posts.findUnique({ where: { slug } }) if (!post) notFound() // renders the nearest not-found.tsx return <article>{post.body}</article> }
Route Collision Rules
Static segments take priority over dynamic ones:
app/blog/featured/page.tsx → /blog/featured (static wins) app/blog/[slug]/page.tsx → /blog/:slug (only non-"featured" slugs)
Specificity order (high → low): exact > dynamic [segment] > catch-all [...slug] > optional catch-all [[...slug]].
TypeScript: params is a Promise (Next 15+)
// WRONG (Next 14 style — type error in Next 15) export default function Page({ params }: { params: { slug: string } }) { return <div>{params.slug}</div> } // CORRECT (Next 15+) export default async function Page({ params }: { params: Promise<{ slug: string }> }) { const { slug } = await params return <div>{slug}</div> }