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
route
file 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/
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 Post: {params.slug}
}
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 (
<>
Return you page or component
)
}
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 === undefined
and 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.tsx
with no params - Visiting
/docs/post1
→ rendersapp/docs/[[...slug]]/page.tsx
withparams.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 (
Catch-all Docs Page
{parts.length === 0 && This is the main docs home.}
{parts.length === 1 && Showing doc: {post}}
{parts.length === 2 && Inside section “{section}” of {post}}
{parts.length >= 3 && (
Deep path: {post} / {section} / {id}… ({parts.join(' / ')})
)}
);
};
export default DocsCatchAll;