skip to content

Dynamic Segments and Catch-Alls

How one file serves a whole family of URLs, and how you tell Next which of those URLs to prerender. Expect to be asked the difference between a catch-all and an optional catch-all, and where params actually arrive.

part ofNext.jsoverview, primer and where to startread it →
on this pageshow

explore

questions

5

In a Next.js App Router project you create app/blog/[slug]/page.tsx. Which URLs does that one file serve, and how does the page component read the slug value?

level: juniorimportance: must knowfreq 85%

answer

  1. square brackets in the folder name
  2. one segment, any value
  3. keyed by the folder name
  4. always a string, never a number
  5. params is awaited in Next 15+

basics

~20 s

app/blog/[slug]/page.tsx serves every single-segment URL under /blog, such as /blog/hello and /blog/42. Next passes the matched text to the page in the params prop as { slug: 'hello' }; since Next 15 params is a Promise, so await it.

solid answer

~40 s

A square-bracket folder is a **dynamic segment**: `app/blog/[slug]/page.tsx` matches `/blog/hello-world`, `/blog/why-next`, `/blog/42` — one segment, any value, one file. The matched text arrives on the page in the `params` prop, keyed by the folder name, so `params.slug` is `'hello-world'`. Since Next 15, `params` is a Promise, so a server page writes `const { slug } = await params`, and a client page unwraps it with React's `use()`. The value is always a string: `[id]` gives you `'42'`, never the number `42`, so convert and validate before using it in a query. A dynamic segment matches exactly one segment — `/blog/2024/recap` and bare `/blog` are not matched by `[slug]`. Nest more bracketed folders for more values, and export `generateStaticParams` when you want the known URLs prerendered at build time.

go deeper

for a junior

Know that a bracketed folder name matches any single URL segment and that the value arrives in the page's params prop under that exact name. Say plainly that the value is a string.

for a middle

Explain that params is a Promise in Next 15 and later and how each rendering context reads it — await in a Server Component, use() in a Client Component — and where params stops and searchParams begins.

for a senior

Show that a segment value is unvalidated user input: describe how you parse and reject bad values before they reach a query, and how the route's prerendering story changes once generateStaticParams enumerates the values.

for a principal

Own the URL shape itself. Be ready to argue when an identifier belongs in the path versus the query string, what that choice costs in cacheability and shareability, and how slug stability affects links and analytics over years.

## What a dynamic segment is In the App Router, folders under `app/` map to URL segments and a `page` file makes a segment publicly routable. A folder whose name is wrapped in square brackets is a **dynamic segment**: instead of matching one literal path piece, it matches *any* value in that position and hands the matched text to your code. So `app/blog/[slug]/page.tsx` is a single file that serves an unbounded family of URLs: `/blog/hello-world`, `/blog/why-next`, `/blog/42`. The name inside the brackets is not magic beyond one thing — it is the key you read the value under. `[slug]` produces `params.slug`; `[id]` produces `params.id`. ## Where the matched value arrives Next passes the segment values to the route as a prop called `params`. The page for the segment receives it, and so does the `layout` for that segment and `generateMetadata` exported from it. Since Next 15, `params` is a **Promise**, not a plain object. In a Server Component that means an async page: ```tsx export default async function Page({ params }: { params: Promise<{ slug: string }> }) { const { slug } = await params return <h1>{slug}</h1> } ``` In a Client Component page you unwrap the same Promise with React's `use()` hook instead of `await`. The reason for the Promise is that it lets Next start rendering the tree before the request-bound values have to be resolved. ## One segment means exactly one This is the most common misreading. `[slug]` matches one path segment, no more and no less: - `/blog/hello` → matches, `slug` is `'hello'` - `/blog/2024/recap` → does **not** match; that is two segments - `/blog` → does **not** match; there is no value for the segment If you need a variable number of segments you reach for a catch-all (`[...slug]`) or an optional catch-all (`[[...slug]]`) instead. If you need a fixed number, nest folders. ## Several dynamic segments compose `app/shop/[category]/[id]/page.tsx` serves `/shop/shoes/42`, and `params` resolves to `{ category: 'shoes', id: '42' }`. Every bracketed folder on the matched path contributes exactly one key, named after the folder. Names must be distinct along a path — two segments both called `[id]` would collide on the same key. ## params is the path; searchParams is the query string `params` carries **path** segments only. Everything after `?` — `/blog/hello?ref=twitter` — is not in `params`; it arrives in the separate `searchParams` prop, which only `page` receives. Candidates who try to read `params.ref` for a query parameter are showing they have not internalised the split. ## Values are always strings A URL is text, so Next does not coerce types. `/products/42` gives `params.id === '42'`. Two consequences worth stating in an interview: ```tsx const { id } = await params const numericId = Number.parseInt(id, 10) if (!Number.isInteger(numericId)) { // reject rather than passing an arbitrary string into a data query } ``` First, convert deliberately. Second, treat the value as untrusted user input: it is whatever the visitor typed in the address bar, so validate it before it reaches a database call or a filesystem path. ## What a dynamic segment does not give you It does not validate anything — any value matches, and it is your job to decide what a legal one looks like. And it does not, by itself, prerender anything: because Next cannot enumerate the possible URLs, a dynamic route is rendered when a request arrives, unless you export `generateStaticParams` to tell Next which concrete values to build ahead of time. ## The mental model to say out loud "A bracketed folder is a parameter in the URL. It matches one segment, it hands me a string under that name, and I get it from the `params` prop, which I have to await in current Next." That sentence, plus the string-not-number caveat and the params/searchParams split, is a complete junior-level answer.

  • For the route app/shop/[category]/[id]/page.tsx and the URL /shop/shoes/42, what does the resolved params object look like?
    `{ category: 'shoes', id: '42' }`. Every bracketed folder on the matched path contributes one key, named after the folder, and both values are strings — `id` is `'42'`, not `42`. Names have to be distinct along a path, otherwise the two segments would fight over the same key.
  • Does the layout.tsx inside a dynamic segment receive params as well?
    Yes. The `layout` for that segment gets the same `params` — also a Promise in Next 15 and later — as does `generateMetadata` exported from the segment. What a layout does not receive is `searchParams`; only `page` gets the query string.
  • How does a Client Component page read params if it cannot use await?
    It unwraps the Promise with React's `use()` hook: `const { slug } = use(params)`. A Client Component cannot be an async function, so `use()` is the supported way to read the same value. Most teams instead keep the page a Server Component, read `params` there, and pass the plain string down as a prop.

saying these in an interview costs you the question

  • Thinks [slug] also matches /blog/2024/recap
  • Uses params.id as a number without converting it
  • Reads params synchronously in Next 15 without awaiting
  • Looks for ?ref=twitter inside params instead of searchParams
  • Treats the segment value as trusted, safe input

context

open as a page

In the Next.js App Router, what is the difference between a [...slug] catch-all segment and a [[...slug]] optional catch-all, and what does params.slug hold in each case?

level: middleimportance: must knowfreq 68%

basics

~20 s

A [...slug] catch-all matches one or more path segments and gives params.slug as an array of strings. A [[...slug]] optional catch-all matches the same URLs plus the bare parent path, where params.slug is undefined rather than an empty array.

open as a page

In a Next.js App Router app with app/blog/[slug]/page.tsx, what does exporting generateStaticParams from that file do, and what shape must it return?

level: middleimportance: must knowfreq 62%

basics

~20 s

generateStaticParams tells Next which concrete values of a dynamic segment to prerender at build time. It returns an array with one object per route — for [slug], entries like { slug: 'hello' } — with keys named after the bracketed folders and string values.

open as a page

A Next.js App Router page needs the ?sort=price query string. Which file receives the searchParams prop, why does a layout.tsx not get one, and what is the resolved value's type?

level: middleimportance: should knowfreq 48%

basics

~20 s

Only page.tsx receives the searchParams prop — a Promise in Next 15 and later that resolves to the parsed query string. Layouts do not: they are preserved and not re-rendered when only the query changes, so a searchParams prop there would go stale.

open as a page

A Next.js catalog serves 50,000 products from app/products/[id]/page.tsx, but generateStaticParams returns only the 500 most popular ids. What does a visitor get when they request one of the other 49,500, and what changes if the segment also exports dynamicParams = false?

level: seniorimportance: should knowfreq 45%

basics

~20 s

With the default dynamicParams = true, an id missing from generateStaticParams is still served: Next renders that page on demand at request time. Exporting dynamicParams = false instead makes any unlisted id return the not-found response.

open as a page