Create dynamic pages with brackets, access route params, and generate static paths with generateStaticParams.
app/
posts/
[id]/
page.tsx/posts/1, /posts/2, or any value for the id segment.params prop, which is now an async Promise you must await.export default async function Post({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
return <h1>Post {id}</h1>;
}params object resolves both at once, once you await it.// Route: app/users/[userId]/posts/[postId]/page.tsx
export default async function PostPage({
params
}: {
params: Promise<{ userId: string; postId: string }>
}) {
const { userId, postId } = await params;
return <div>User {userId}, Post {postId}</div>;
}const { id } = await params;
const post = await getPost(id);/docs/a, /docs/a/b, and /docs/a/b/c alike? Catch-all segments do exactly that with [...slug].app/
docs/
[...slug]/
page.tsx/docs/a, /docs/a/b, /docs/a/b/c, etc.slug param is always an array of path segments, available after awaiting params.export default async function Docs({ params }: { params: Promise<{ slug: string[] }> }) {
const { slug } = await params;
const path = slug.join("/");
return <h1>{path}</h1>;
}[[...slug]] for breadcrumb navigation./blog and /blog/2025/january/post? Double the brackets to [[...slug]] and the catch-all becomes optional.app/
blog/
[[...slug]]/
page.tsx/blog, /blog/post-1, /blog/2025/january/post, etc.params first to read it.const { slug } = await params;
const segments = slug ?? [];
const depth = segments.length;generateStaticParams() to pre-build specific dynamic pages at deploy time.export async function generateStaticParams() {
const posts = await getPosts();
return posts.map((post) => ({
id: post.id.toString(),
}));
}
export default async function Post({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
// This page is pre-built for every post at build time
return <h1>Post {id}</h1>;
}revalidate for incremental static regeneration to update stale pages.export const revalidate = 3600; // Rebuild every hourCreate a folder with the segment name wrapped in square brackets, like app/blog/[slug]/page.tsx. The bracket syntax tells Next.js that this segment is dynamic and will match any value at that URL position.
Dynamic page components receive a params prop, which is an async Promise containing your segment names as keys. For a route like [slug], type it as async function Page({ params }: { params: Promise<{ slug: string }> }), then run const { slug } = await params before using it.
The [...slug] (catch-all) requires at least one segment and returns 404 for the base path, while [[...slug]] (optional catch-all) also matches the parent route with no segments. Use optional catch-all when you want one component to handle both /docs and /docs/getting-started.
Yes, you can nest multiple dynamic folders, such as app/shop/[category]/[productId]/page.tsx. Both category and productId will be available on the params object simultaneously.
By default, Next.js falls back to server-rendering the missing page on first request. You can change this behavior by exporting export const dynamicParams = false from the page, which will make unlisted routes return a 404 instead.