Dynamic, Catch-all & Optional Catch-all Routes
A folder named [id] matches one segment: /courses/101 → params.id is "101". Use this for blog posts, products, and user profiles.
Dynamic segment — [id]
A folder named [id] matches one segment: /courses/101 → params.id is "101". Use this for blog posts, products, and user profiles.
In App Router, page props receive params (often as a Promise in newer Next.js versions — await params when your version requires it).
Real-life example: [id] is a hotel room number. The hallway shape is the same; the room number changes which door you open.
type Props = {
params: Promise<{ id: string }>;
};
export default async function CoursePage({ params }: Props) {
const { id } = await params;
// Example: fetch course by id
// const course = await getCourse(id);
return (
<article>
<h1>Course #{id}</h1>
<p>Dynamic route for one Rishtaara course.</p>
</article>
);
}app/courses/[id]/page.tsx → /courses/abc
→ /courses/nextjs-basics
# Link example:
# <Link href={`/courses/${course.id}`}>{course.title}</Link>Catch-all — [...slug]
[...slug] captures one or more segments as an array. /docs/a/b/c → slug = ["a", "b", "c"]. It does not match the parent path alone unless you also add a page there.
Great for docs trees, nested categories, and file-path style URLs.
Real-life example: Catch-all is a suitcase that holds every remaining layer of clothing — one bag for the rest of the trip itinerary.
type Props = {
params: Promise<{ slug: string[] }>;
};
export default async function DocsPage({ params }: Props) {
const { slug } = await params;
const path = slug.join("/");
return (
<article>
<h1>Docs: /{path}</h1>
<ol>
{slug.map((part) => (
<li key={part}>{part}</li>
))}
</ol>
</article>
);
}
// Matches:
// /docs/getting-started
// /docs/nextjs/routing/dynamic
// Does NOT match /docs alone (add app/docs/page.tsx for that)Optional catch-all — [[...slug]]
Double brackets [[...slug]] also match the route with zero segments. So app/shop/[[...slug]]/page.tsx can serve /shop and /shop/shoes/red.
When there are no segments, slug may be undefined — always guard for that.
Real-life example: Optional catch-all is a bus that stops at the depot with no passengers and also stops at every town along the route.
type Props = {
params: Promise<{ slug?: string[] }>;
};
export default async function ShopPage({ params }: Props) {
const { slug } = await params;
if (!slug || slug.length === 0) {
return <h1>All products — Rishtaara Shop</h1>;
}
return (
<h1>
Category: {slug.join(" / ")}
</h1>
);
}
// Matches:
// /shop
// /shop/books
// /shop/books/programming[id] → exactly one segment
[...slug] → one or more segments (required)
[[...slug]] → zero, one, or more segments (optional)