Next.js Dynamic Routes

Create dynamic pages with bracket folders, read route params, and prebuild paths with generateStaticParams.

TL;DR

  1. Name a folder [slug] to match any value in that segment.
  2. Read the value by awaiting the params prop in your page.
  3. Prebuild known paths at build time with generateStaticParams().

Dynamic Segments

    [slug]

    Match any single value in a URL segment by naming the folder in brackets.

    // app/blog/[slug]/page.tsx
    export default async function Page({
      params,
    }: {
      params: Promise<{ slug: string }>;
    }) {
      const { slug } = await params;
      return <h1>{slug}</h1>;
    }
    PageProps

    Type the page props from the route path with the global PageProps helper.

    export default async function Page(
      props: PageProps<'/blog/[slug]'>
    ) {
      const { slug } = await props.params;
      return <h1>{slug}</h1>;
    }
    Multiple Params

    Nest bracket folders to capture more than one value from the URL.

    // app/shop/[category]/[item]/page.tsx
    const { category, item } = await params;
    // /shop/shoes/42 -> 'shoes', '42'

Catch-All Segments

    [...slug]

    Match one or more segments and receive them as an array of strings.

    // app/docs/[...slug]/page.tsx
    const { slug } = await params;
    // /docs/a/b -> ['a', 'b']
    [[...slug]]

    Make the catch-all optional so it also matches the path with no segments.

    // app/docs/[[...slug]]/page.tsx
    const { slug } = await params;
    // /docs      -> undefined
    // /docs/a/b  -> ['a', 'b']
    Join Segments

    Rebuild the full path from the array when you look up nested content.

    const { slug = [] } = await params;
    const path = slug.join('/');
    const doc = await getDoc(path);

Static Generation

    generateStaticParams()

    Return the param values to prerender when the app is built.

    export async function generateStaticParams() {
      const posts = await getPosts();
      return posts.map((post) => ({
        slug: post.slug,
      }));
    }
    dynamicParams

    Return a 404 for unlisted paths; this option is unavailable with Cache Components.

    // app/blog/[slug]/page.tsx
    export const dynamicParams = false;
    notFound()

    Show the 404 page when the slug does not match any real content.

    import { notFound } from 'next/navigation';
    
    const post = await getPost(slug);
    if (!post) notFound();

Params in Other Places

    generateMetadata()

    Read the same params to set a page title that matches the content.

    export async function generateMetadata({
      params,
    }: {
      params: Promise<{ slug: string }>;
    }) {
      const { slug } = await params;
      return { title: slug };
    }
    Route Handlers

    Receive params through the second argument of a route.ts handler.

    // app/api/posts/[id]/route.ts
    export async function GET(
      _req: Request,
      { params }: { params: Promise<{ id: string }> }
    ) {
      const { id } = await params;
      return Response.json({ id });
    }
    useParams()

    Read route params inside a Client Component without passing props down.

    'use client';
    import { useParams } from 'next/navigation';
    
    export function Crumb() {
      const { slug } = useParams<{ slug: string }>();
      return <span>{slug}</span>;
    }

Tips

  1. Type pages with the global PageProps helper, so params stays in sync with the folder names in your route.
  2. Call notFound() when a lookup returns nothing, so unknown slugs show your 404 page instead of a broken layout.

Warnings

  1. The params prop is a promise in Next.js 16, so reading params.slug without await gives you undefined.
  2. Values in params are always strings, so convert an id with Number() before passing it to a database query.

In Practice

FAQ