Dynamic Routes
Most real applications need routes whose path depends on data — a blog post slug, a product ID, a username. Next.js handles this with dynamic route segments: wrap a folder name in square brackets, like
[id], and that segment becomes a variable the page can read at request time instead of a fixed string.Folder structure
Text
app/
blog/
[slug]/
page.tsxVisiting
/blog/hello-world or /blog/nextjs-tips both match this route, and in each case Next.js supplies the matched segment value through the page's params prop.app/blog/[slug]/page.tsx
TSX
type Props = {
params: { slug: string }
}
export default function BlogPostPage({ params }: Props) {
const { slug } = params
return <h1>Post: {slug}</h1>
}Multiple dynamic segments
A route can contain more than one dynamic segment. Each bracketed folder becomes its own key on the
params object.Folder structure
Text
app/
shop/
[category]/
[productId]/
page.tsxapp/shop/[category]/[productId]/page.tsx
TSX
type Props = {
params: { category: string; productId: string }
}
export default function ProductPage({ params }: Props) {
const { category, productId } = params
return (
<p>
Category: {category}, Product: {productId}
</p>
)
}Note
Visiting
/shop/shoes/42 gives you { category: "shoes", productId: "42" }.Params are always strings
Regardless of what the segment "looks like" in the URL, Next.js always hands you a
string (or, for catch-all routes, an array of strings). Even if a segment is conceptually a numeric ID, you must parse it yourself before using it as a number.Parsing a numeric param
TSX
export default function ProductPage({
params,
}: {
params: { productId: string }
}) {
const productId = Number(params.productId)
if (Number.isNaN(productId)) {
// handle invalid input, e.g. call notFound()
}
return <p>Product #{productId}</p>
}Warning
Don't assume a param is a valid number, UUID, or slug just because your app only ever links to valid values. Users can type any URL directly, so validate and parse before trusting it.
Next.js 15: params became a Promise
This is a genuinely important, breaking-ish change. In Next.js 13 and 14,
params (and searchParams) were plain synchronous objects. Starting with Next.js 15, in Server Components params is a Promise that you must await before reading its properties — part of a broader move to let Next.js start rendering before all route params are fully resolved.Before — Next.js 13/14
TSX
export default function Page({
params,
}: {
params: { slug: string }
}) {
return <h1>{params.slug}</h1>
}After — Next.js 15+
TSX
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params
return <h1>{slug}</h1>
}Note
The same change applies to
searchParams on the page component, and to the second argument of generateMetadata. If you're on Next.js 15+ and see a type error or a runtime warning about accessing a param synchronously, this is almost always the cause — check your Next.js version before copying examples from older tutorials or documentation.A folder named
[name]creates a dynamic segment matched against any value in that URL position.The matched value is available on the
paramsprop, keyed by the folder name.A route can have multiple dynamic segments, each contributing its own key to
params.paramsvalues are always strings — parse them yourself if you need a number, boolean, etc.Next.js 15+ makes
params(andsearchParams) aPromisein Server Components — you mustawaitit.