generateStaticParams

generateStaticParams and Root Params

A dynamic segment such as catalog/[id] is rendered on demand unless you tell the build which values exist. generateStaticParams is that list: it runs before the page, may fetch, and returns one object per route.

Prerendering every book in the catalog (app/catalog/[id]/page.js)Shell
export const dynamicParams = false;   // anything not listed here is a 404
export async function generateStaticParams() {
  const books = await listBooks(20);
  return books.map((b) => ({ id: b.id }));
}

next build reports what it produced — ● for pages generated from generateStaticParams, ○ for a static route with no parameters, ƒ for one rendered per request:

Output of 29
├ ○ /books
├   /catalog/[id]
│ ├ ● /catalog/bk_1
│ └ ● [+4 more paths]
├ ƒ /greet                      <- reads cookies()
└ ƒ /seq/[id]                   <- no generateStaticParams

Return the full list for a catalog of hundreds; return posts.slice(0, 10) for a blog of thousands and let the rest render on first visit. dynamicParams decides the fate of the unlisted ones: true (the default) renders and caches them on demand, false answers 404. An empty array means "none at build time, all at runtime", except with Cache Components, where at least one value is required.

A dynamic segment above the root layout — app/[lang]/layout.js — is a root param, shared by every route beneath. Next.js 16.3 10,514 exports each one as an async getter from next/root-params named after the folder, so shared server code reads it without prop drilling: import { lang } from "next/root-params", then await lang(). Kebab-case segments such as [post-slug] are rejected as invalid identifiers, and the getters work in Server Components and server-side utilities only — not in Client Components, Server Actions, Route Handlers or unstable_cache. Caching and Revalidation shows the payoff: only the root params a cached function reads join its cache key.