Next.js Dynamic Routes
Create dynamic pages with bracket folders, read route params, and prebuild paths with generateStaticParams.
TL;DR
- Name a folder
[slug]to match any value in that segment. - Read the value by awaiting the
paramsprop in your page. - Prebuild known paths at build time with
generateStaticParams().
Dynamic Segments
[slug]Match any single value in a URL segment by naming the folder in brackets.
// app/blog/[slug]/page.tsx
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
return <h1>{slug}</h1>;
}PagePropsType the page props from the route path with the global PageProps helper.
export default async function Page(
props: PageProps<'/blog/[slug]'>
) {
const { slug } = await props.params;
return <h1>{slug}</h1>;
}Multiple ParamsNest bracket folders to capture more than one value from the URL.
// app/shop/[category]/[item]/page.tsx
const { category, item } = await params;
// /shop/shoes/42 -> 'shoes', '42'Catch-All Segments
[...slug]Match one or more segments and receive them as an array of strings.
// app/docs/[...slug]/page.tsx
const { slug } = await params;
// /docs/a/b -> ['a', 'b'][[...slug]]Make the catch-all optional so it also matches the path with no segments.
// app/docs/[[...slug]]/page.tsx
const { slug } = await params;
// /docs -> undefined
// /docs/a/b -> ['a', 'b']Join SegmentsRebuild the full path from the array when you look up nested content.
const { slug = [] } = await params;
const path = slug.join('/');
const doc = await getDoc(path);Static Generation
generateStaticParams()Return the param values to prerender when the app is built.
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map((post) => ({
slug: post.slug,
}));
}dynamicParamsReturn a 404 for unlisted paths; this option is unavailable with Cache Components.
// app/blog/[slug]/page.tsx
export const dynamicParams = false;notFound()Show the 404 page when the slug does not match any real content.
import { notFound } from 'next/navigation';
const post = await getPost(slug);
if (!post) notFound();Params in Other Places
generateMetadata()Read the same params to set a page title that matches the content.
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
return { title: slug };
}Route HandlersReceive params through the second argument of a route.ts handler.
// app/api/posts/[id]/route.ts
export async function GET(
_req: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
return Response.json({ id });
}useParams()Read route params inside a Client Component without passing props down.
'use client';
import { useParams } from 'next/navigation';
export function Crumb() {
const { slug } = useParams<{ slug: string }>();
return <span>{slug}</span>;
}Tips
- Type pages with the global
PagePropshelper, soparamsstays in sync with the folder names in your route. - Call
notFound()when a lookup returns nothing, so unknown slugs show your 404 page instead of a broken layout.
Warnings
- The
paramsprop is a promise in Next.js 16, so readingparams.slugwithoutawaitgives you undefined. - Values in
paramsare always strings, so convert an id withNumber()before passing it to a database query.
In Practice
Build one page file that serves every blog post and prebuilds the known ones.
- generateStaticParams lists every slug so those pages are built ahead of time.
- The page awaits params, because params is a promise in current Next.js.
- A missing post calls notFound, which renders the nearest not-found page.
- The same file now serves every post without any extra routing code.
// app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation';
import { getPost, getPosts } from '@/lib/posts';
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map((post) => ({ slug: post.slug }));
}
export default async function Page(
props: PageProps<'/blog/[slug]'>
) {
const { slug } = await props.params;
const post = await getPost(slug);
if (!post) notFound();
return (
<article>
<h1>{post.title}</h1>
<p>{post.body}</p>
</article>
);
}FAQ
Wrap a folder name in square brackets, like app/blog/[slug]/page.tsx. That page then matches /blog/anything. Read the value by awaiting the params prop.
It returns the list of param values to build ahead of time. Next.js renders those pages during next build, so they load instantly. Other values render on the first request.
[slug] matches exactly one segment. [...slug] is a catch-all that matches one or more segments and gives you an array. [[...slug]] also matches the path with no extra segments.