Dynamic Segments

Wrap a folder name in square brackets and the segment matches any single value, which arrives in the params prop of the segment's page, layout, route and generateMetadata. params is a promise: await it in a Server Component, or unwrap it with React 7,897 's use in a Client Component.

app/blog/[slug]/page.jsJavaScript
import { posts } from '../../_lib/posts'
export function generateStaticParams() {
  return posts.map((p) => ({ slug: p.slug }))
}
export default async function Post({ params }) {
  const { slug } = await params
  return <h1>{posts.find((p) => p.slug === slug)?.title ?? slug}</h1>
}

generateStaticParams turns a dynamic route into prerendered HTML. It returns the parameter sets worth building ahead of time, and the build table marks each of them ●; URLs outside the list are still served, rendered on first request. fetch calls inside it are memoized, so listing the same data in a layout and in several generateStaticParams functions costs one request.

Output of 9
├   /blog/[slug]
│ ├ ● /blog/hello-world
│ └ ● /blog/app-router

Parameter values are strings and come from the address bar, so treat them as untrusted input: validate, then call notFound() from next/navigation for anything you do not recognize. Segments nest freely — app/[category]/[item]/page.js yields { category, item } — and a dynamic segment above the root layout becomes a root parameter, readable anywhere on the server through next/root-params, which is how app/[lang]/layout.js powers internationalized sites.