Generate Static Params

The generateStaticParams function can be used in combination with dynamic route segments to define the list of route segment parameters that will be statically generated at build time instead of on-demand.

generateStaticParams()

The generateStaticParams function runs at build time before the corresponding Layouts or Pages are generated. It will not be called again during revalidation (ISR).

app/blog/[slug]/page.js
export async function generateStaticParams() {
  const posts = await getPosts();

  return posts.map((post) => ({
    slug: post.slug,
  }));
}

The value returned by generateStaticParams is used to generate a list of static segments, which each receive the value of the returned object as their params prop.

app/blog/[slug]/page.js
export default function Page({ params }) {
  const { slug } = params;

  return ...
}

export async function generateStaticParams() {
  const posts = await getPosts();

  return posts.map((post) => ({
    slug: post.slug,
  }));
}

Multiple Dynamic Segments in a Route

You can generate segments for dynamic segments above the current layout or page, but not below. For example, given the app/products/[categorySlug]/[productId] route:

  • app/products/[categorySlug]/[productId]/page.js can generate segments for both [categorySlug] and [productId].
  • app/products/[categorySlug]/layout.js can only generate segments for [categorySlug].

There are two approaches to generating segments for a route with multiple dynamic segments:

Generate segments from the bottom up

Generate multiple dynamic segments from the child segment.

app/products/[categorySlug]/[productId]/page.js
// Generate segments for both [categorySlug] and [productId]
export async function generateStaticParams() {
  const rows = await query('SELECT category, slug FROM products');

  return rows.map((row) => ({
    categorySlug: row.slug,
    productId: row.slug,
  }));
}

Generate segments from the top down

Generate the parent segments first and use the result to generate the child segments.

app/products/[categorySlug]/layout.js
// Generate segments for [categorySlug]
export async function generateStaticParams() {
  const rows = await query('SELECT category FROM products');

  return rows.map((row) => ({
    categorySlug: row.category,
  }));
}

A child segment's generateStaticParams function is executed once for each segment a parent generateStaticParams generates. The child's generateStaticParams function receives the object returned from the parent segment's generateStaticParams function that can be used to dynamically generate its own segments.

app/products/[categorySlug]/[productId]/page.js
// categorySlug prop passed by parent segment's `generateStaticParams` function
export async function generateStaticParams({ categorySlug }) {
  const rows = await query(
    'SELECT productId FROM products WHERE categorySlug = ?',
    categorySlug,
  );

  return rows.map((row) => ({
    productId: row.productId,
  }));
}

If content is fetched within the generateStaticParams function using a fetch request, the requests are automatically deduped. This means a fetch request with the same arguments across multiple generateStaticParams, Layouts, and Pages will only be made once.

Catch-all and Optional Catch-all Dynamic Segments

If a dynamic segment is a catch-all dynamic segment, such as [...slug] or an optional catch-all dynamic segment such as [[...slug]], then the value of the generated static segment should be an array of strings:

app/products/[...slug]/page.js
export async function generateStaticParams() {
  return [
    { slug: ['electronics', '1'] }, // -> /products/electronics/1,
    { slug: ['clothing', '2'] },  // -> /products/clothing/2,
    { slug: ['books', '3'] }  // -> /products/books/3
    // ...
  ];
}