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

  1. Add square brackets around the segment name in your app folder.
  2. 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 === 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 → renders app/docs/page.tsx with no params
  • Visiting /docs/post1 → renders app/docs/[[...slug]]/page.tsx with params.slug === ['post1']
  • Visiting /docs/post1/comments/2 → same catch-all page with params.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;