Skip to content

Dynamic Segments and Route Groups

Bracketed folders turn URL parts into params you read from a Promise, while parenthesized and underscored folders reshape your file tree without changing the URL.

A folder named [id] captures one URL part as a param. In current Next.js params is a Promise, so you await it. The same is true for searchParams.

app/blog/[id]/page.tsx
export default async function Post({
params,
searchParams,
}: {
params: Promise<{ id: string }>
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}) {
const { id } = await params
const { sort } = await searchParams
return <article>Post {id} sorted by {sort}</article>
}

Use [...slug] to capture many segments as an array. Use [[...slug]] to make it optional so the parent route also matches with no extra segments.

app/
docs/
[...slug]/page.tsx // matches /docs/a and /docs/a/b/c
shop/
[[...slug]]/page.tsx // matches /shop AND /shop/a/b
app/docs/[...slug]/page.tsx
export default async function Docs({
params,
}: {
params: Promise<{ slug: string[] }>
}) {
const { slug } = await params
return <h1>{slug.join(' / ')}</h1>
}

generateStaticParams prerenders dynamic routes

Section titled “generateStaticParams prerenders dynamic routes”

Export generateStaticParams to tell Next.js which param values to prerender at build time. Each returned object becomes one statically generated page.

app/blog/[id]/page.tsx
export function generateStaticParams() {
return [{ id: '1' }, { id: '2' }, { id: '3' }]
}

A folder in parentheses like (marketing) is a route group: it organizes files and can hold its own shared layout, but its name is stripped from the URL. A folder with a leading underscore like _components is a private folder that opts out of routing entirely.

app/
(marketing)/
layout.tsx // layout for marketing pages only
about/page.tsx // URL is /about, NOT /marketing/about
(shop)/
cart/page.tsx // URL is /cart
_components/ // private, never a route
Button.tsx
graph TD
  A["[id] dynamic segment"] --> U1["changes URL: /blog/42"]
  B["[...slug] catch-all"] --> U2["changes URL: /docs/a/b"]
  C["(marketing) route group"] --> U3["no URL change"]
  D["_components private"] --> U4["not routable"]
Folders that do vs do not change the URL
In current Next.js, how do you read the id from a dynamic segment?
What does [[...slug]] provide over [...slug]?
What is generateStaticParams used for?
What is the effect of a (marketing) route group on the URL?