Skip to content

Dynamic Routes

Dynamic routes let a single file handle many URLs, such as every blog post or every product page, by capturing part of the URL as a parameter. This lesson covers creating, reading, and pre-rendering dynamic routes.

Creating a Dynamic Segment

Wrapping a folder name in square brackets, like [id], tells Next.js that segment of the URL is a variable rather than a fixed string. A single folder app/products/[id]/page.tsx then matches /products/1, /products/42, /products/anything — any value in that position.

Inside the page component, Next.js automatically provides the captured value through the params prop, keyed by whatever name you used inside the brackets.

// app/products/[id]/page.tsx
export default function ProductPage({
  params,
}: {
  params: { id: string };
}) {
  return <h1>Product #{params.id}</h1>;
}

Visiting /products/42 renders "Product #42"; the same file handles every product ID without duplication.

Dynamic Segment Syntax Options

[id]            // one segment: /products/42
[...slug]       // catch-all: /docs/a/b/c → slug: ["a", "b", "c"]
[[...slug]]     // optional catch-all: also matches /docs (slug: undefined)
  • [id] matches exactly one URL segment and delivers it as a string.
  • [...slug] matches one or more remaining segments and delivers them as a string array.
  • [[...slug]] behaves like [...slug] but also matches the parent path with zero segments.
  • Parameter names inside brackets become the exact key used on the params object.

Dynamic Routes Cheat Sheet

Segment types and what params looks like for each.

File Path URL params Value
app/posts/[id]/page.tsx /posts/7 { id: "7" }
app/shop/[...path]/page.tsx /shop/shoes/nike { path: ["shoes", "nike"] }
app/docs/[[...path]]/page.tsx /docs { path: undefined }
app/docs/[[...path]]/page.tsx /docs/a/b { path: ["a", "b"] }

Pre-Rendering Dynamic Routes with generateStaticParams

By default, a dynamic route renders on demand for each request. Exporting an async generateStaticParams function tells Next.js which specific parameter values to pre-render into static HTML at build time, similar in spirit to getStaticPaths in the Pages Router.

// app/posts/[id]/page.tsx
export async function generateStaticParams() {
  const posts = await fetch("https://api.example.com/posts").then((r) => r.json());
  return posts.map((post: any) => ({ id: String(post.id) }));
}

export default async function PostPage({
  params,
}: {
  params: { id: string };
}) {
  const post = await fetch(`https://api.example.com/posts/${params.id}`).then((r) => r.json());
  return <h1>{post.title}</h1>;
}

Each object returned from generateStaticParams becomes one statically-generated page at build time.

Combining Multiple Dynamic Segments

A route can include more than one dynamic segment at different nesting levels. Each bracketed folder contributes its own key to the combined params object.

app/
  shop/
    [category]/
      [productId]/
        page.tsx      // /shop/:category/:productId

// params: { category: string, productId: string }

Common Mistakes

  • Using two different dynamic segment names at the same folder level (e.g. [id] and [slug] as siblings), which Next.js does not allow.
  • Forgetting that catch-all params ([...slug]) are always arrays, even with a single segment.
  • Not exporting generateStaticParams when full static generation is expected, leaving the route dynamically rendered instead.
  • Assuming params values are already parsed as numbers — they always arrive as strings.

Key Takeaways

  • Square-bracket folder names ([id]) create dynamic route segments.
  • params delivers captured values into the page component automatically.
  • [...slug] captures multiple segments as an array; [[...slug]] makes that catch-all optional.
  • generateStaticParams pre-renders specific dynamic routes into static HTML at build time.
  • Route params always arrive as strings and may need explicit parsing (e.g. Number(params.id)).

Pro Tip

If a dynamic route's data rarely changes and the total number of possible values is small (like a fixed set of product categories), pre-render all of them with generateStaticParams — you get the performance of static HTML with none of the routing complexity of separate static files.