Technology · Next.js

Next.js Dynamic Routes

Create dynamic pages with brackets, access route params, and generate static paths with generateStaticParams.

TL;DR
  1. 01Wrap folder names in brackets to create dynamic segments.
  2. 02Access params through the params prop passed to pages.
  3. 03Use generateStaticParams to pre-build dynamic pages at compile time.

Basic Dynamic Routes

  • Create a dynamic route by wrapping a folder name in square brackets.
    app/
      posts/
        [id]/
          page.tsx
  • This matches /posts/1, /posts/2, or any value for the id segment.
  • Access the dynamic parameter through the params prop in the page component.
    export default function Post({ params }: { params: { id: string } }) {
      return <h1>Post {params.id}</h1>;
    }
  • Each dynamic segment creates a separate route that can load different data.
  • URL parameters are always strings, so parse them as needed.

Accessing Route Parameters

  • The params object contains all dynamic segments from the URL path.
    // Route: app/users/[userId]/posts/[postId]/page.tsx
    export default function PostPage({ 
      params 
    }: { 
      params: { userId: string; postId: string } 
    }) {
      return <div>User {params.userId}, Post {params.postId}</div>;
    }
  • Fetch data using the params to load content specific to that route.
    const post = await getPost(params.id);
  • Pass params to layout components if they need this information.
  • Nested dynamic segments work just like single-level ones.

Catch-All Routes

  • Use [...slug] to capture all remaining segments in one parameter.
    app/
      docs/
        [...slug]/
          page.tsx
  • This matches /docs/a, /docs/a/b, /docs/a/b/c, etc.
  • The slug param is always an array of path segments.
    export default function Docs({ params }: { params: { slug: string[] } }) {
      const path = params.slug.join("/");
      return <h1>{path}</h1>;
    }
  • Use catch-all routes for nested documentation or content sites.
  • Combine with optional catch-all [[...slug]] for breadcrumb navigation.

Optional Catch-All Routes

  • Use [[...slug]] to make the catch-all optional, matching parent too.
    app/
      blog/
        [[...slug]]/
          page.tsx
  • This matches /blog, /blog/post-1, /blog/2025/january/post, etc.
  • The slug param is an array only if segments exist, otherwise undefined.
    const slug = params.slug ?? [];
    const depth = slug.length;
  • Use optional catch-all when a single page handles multiple URL patterns.
  • Great for flexible navigation menus and hierarchical content.

Static Generation with Dynamic Routes

  • Export generateStaticParams() to pre-build specific dynamic pages.
    export async function generateStaticParams() {
      const posts = await getPosts();
      return posts.map((post) => ({
        id: post.id.toString(),
      }));
    }
    
    export default function Post({ params }) {
      // This page is pre-built for every post at build time
      return <h1>Post {params.id}</h1>;
    }
  • Pre-built pages load instantly with no server delay.
  • Pages not in generateStaticParams are generated on-demand the first time.
  • Use revalidate for incremental static regeneration to update stale pages.
    export const revalidate = 3600; // Rebuild every hour
  • Great for blogs, product pages, and public documentation.

Tip: Use generateStaticParams() for high-traffic pages like blog posts to pre-build them at deploy time for instant page loads.

Warning: If generateStaticParams doesn't include a route, it will be generated on first request which causes a slow cold start on serverless.

Next.js Internationalization