A dynamic route lets your application handle URLs whose segments aren’t known ahead of time. In Next.js, you create a dynamic segment by wrapping a folder or file name in square brackets []. The App Router then maps any matching URL into that segment and passes its value as a params object to your page component. This approach keeps your routing flexible and your code DRY, whether you’re building user profiles /users/[username] or blog posts /posts/[id].
What are Dynamic Routes?
Dynamic Routes allow you to define URL patterns with placeholders instead of hard-coding every path. When a request arrives, Next.js fills in those placeholders and hands you the values to render the right content.
Why use them?
- Flexibility: One
routefile can handle dozens or thousands of URLs without manual setup. - Scalability: Your codebase stays lean as your app grows.
Setting up Dynamic Routes
- Add square brackets around the segment name in your app folder.
- Next.js will treat that bracketed folder (or file) as a dynamic segment
app/
├── layout.js
├── page.js
└── blog/
└── [slug_Or_Any_Name]/
└── page.js
Here app/blog/[slug]/page.js file will be rendered for the /blog/<value> route.
Working with Route Parameters
When you export an async page component inside a dynamic folder, Next.js injects a params object containing your segment value(s):
// app/blog/[slug]/page.tsx
interface Params { slug: string }
const BlogPost = async ({ params }: { params: Params }) => {
// params.slug holds the URL value, e.g. "my-first-post"
return <h1>Post: {params.slug}</h1>
}
export default BlogPost
- The page component must be async because Next.js may fetch data before rendering.
- You can destructure params directly in the function signature.
Catch-All segments
You can create an infinite sub-route under a route.
In general catch-all-segment takes a value. But catch-all-segment can hold all nested routes' value as an array of strings.
Create catch-all segment
- Like dynamic route, make the directory into
[]add...before the directory name.[...slug]
/app/[...path]/page.js
- Get
params
const Product = async ({params} : {params : path: string[]) => {
const {path} = await param;
return (
<>
<h1> Return you page or component </h1>
)
}
export default Product;
Now you can see your component at this URL endpoint.
Optional catch-all segment
If you want to match both the base route and deeper paths with one file, wrap the catch-all in double brackets. This way, no error occurs when no segments are provided. You can make the catch-all segments optional by adding the parameter in double square brackets: [[...slug]]
app/
└── docs/
├── page.tsx # handles the base /docs route
└── [[...slug]]/ # optional catch-all
└── page.tsx # handles /docs/* at any depth
Deep dive into the optional catch-all segment: In Next.js, showing how to serve /docs with its own page and route all deeper paths /docs/post1, /docs/post2, /docs/post1/comments/2, etc. to a single catch-all handler—without triggering a 404 for unknown slugs. You’ll see folder setup, how Next.js maps URLs to params, and how to render your page component unconditionally for any nested route.
Optional catch-all segments use [[...slug]] in your folder name. This matches both the base path /docs with params.slug === undefinedand any depth /docs/* with params.slug as an array of strings
Folder Structure
app/
└── docs/
├── page.tsx # handles the base /docs route
└── [[...slug]]/ # optional catch-all
└── page.tsx # handles /docs/* at any depth
- Visiting
/docs→ rendersapp/docs/page.tsxwith no params - Visiting
/docs/post1→ rendersapp/docs/[[...slug]]/page.tsxwithparams.slug === ['post1'] - Visiting
/docs/post1/comments/2→ same catch-all page withparams.slug === ['post1','comments','2']
NB: We never call notFound() when doc is missing; instead, we show fallback content
Handling params
// app/docs/[[...slug]]/page.tsx
interface Params {
// slug may be undefined (for /docs) or an array of strings
slug?: string[];
}
const DocsCatchAll = async ({ params }: { params: Params }) => {
// Normalize to an array so you can always iterate or destructure safely
const parts = params.slug ?? [];
// e.g. [] for /docs, ['post1'] for /docs/post1, etc.
// Example destructure:
const [post, section, id] = parts;
return (
<div>
<h1>Catch-all Docs Page</h1>
{parts.length === 0 && <p>This is the main docs home.</p>}
{parts.length === 1 && <p>Showing doc: {post}</p>}
{parts.length === 2 && <p>Inside section “{section}” of {post}</p>}
{parts.length >= 3 && (
<p>
Deep path: {post} / {section} / {id}… ({parts.join(' / ')})
</p>
)}
</div>
);
};
export default DocsCatchAll;
Top comments (0)