Dynamic Segments

One File, Many URLs

Wrap a folder name in square brackets to make it a variable. app/products/[id]/page.tsx matches /products/1, /products/42, /products/redmi-note-14 and so on.

app/products/[id]/page.tsx

type Props = { params: Promise<{ id: string }> };

export default async function ProductPage({ params }: Props) {
  const { id } = await params; // Next.js 15+: params is a Promise
  return <h1>Product #{id}</h1>;
}

Example

curl -s http://localhost:3000/products/42 | grep h1

Example output

<h1>Product #<!-- -->42</h1>

Params Are Always Strings

URL values arrive as strings. Convert and validate them before use:

Example

function parseId(raw: string): number | null {
  const n = Number(raw);
  return Number.isInteger(n) && n > 0 ? n : null;
}

for (const raw of ['42', '007', 'abc', '-3', '4.5', '']) {
  console.log(JSON.stringify(raw).padEnd(8), '->', parseId(raw));
}

Output

"42"     -> 42
"007"    -> 7
"abc"    -> null
"-3"     -> null
"4.5"    -> null
""       -> null

Tip: If parseId returns null, call notFound().

Multiple Segments

app/shop/[category]/[productId]/page.tsx matches /shop/mobiles/42 and gives { category: 'mobiles', productId: '42' }.

export default async function Page({
  params,
}: {
  params: Promise<{ category: string; productId: string }>;
}) {
  const { category, productId } = await params;
  return <p>{category} / {productId}</p>;
}