generateStaticParams returns the list of dynamic route values Next.js prerenders at build time, and dynamicParams decides what happens to every value not on that list. With dynamicParams = false, an unlisted value returns 404; with the default true, it renders on first request. Setting false means a new page does not exist until you rebuild.
We checked both settings against this site's own production build on next@15.5.20, then served that build with next start to see what a visitor gets.
Key takeaways
dynamicParams = falsecompiles tofallback: falsein the prerender manifest; the default compiles tofallback: null.- A server running an old build returns 404 for content published after it, with no fallback render.
- The param list should come from the same loader the page reads, never a second hand-kept list.
generateStaticParamsruns at build time, not per request.
What generateStaticParams does
generateStaticParams is an exported function on a dynamic route ([slug], [category]/[slug]) that returns an array of param objects. Next.js renders one static page per object when you run next build.
Our article route returns every published article's category and slug:
import { getArticleParams } from "@/lib/mdx";
/** Every published article is prerendered; unknown routes 404. */
export async function generateStaticParams() {
return getArticleParams();
}
export const dynamicParams = false;
getArticleParams() walks content/<category>/<slug>.mdx, skips drafts, and maps each file to { category, slug }. The path is the route, so the param list cannot disagree with the files on disk.
The result in the last build is plain files in .next/server/app, such as legal/cookies.html, which the host serves without running the page.
dynamicParams true versus false
dynamicParams is a route segment config with two values, and the build manifest records which one each route compiled to. We read both from .next/prerender-manifest.json:
| Route | Source | Manifest fallback | Unlisted value |
|---|---|---|---|
/[category]/[slug] | dynamicParams = false | false | 404 |
/legal/[slug] | default (true) | null | Renders on request |
Both routes call generateStaticParams. The difference is only the unlisted case. Our /legal/[slug] page still returns 404 for /legal/not-a-policy, but through its own notFound() call after the lookup fails, not through the framework.
That distinction matters for crawlers. With false, a mistyped URL costs no render and the 404 is immediate. With true, each unknown value runs your page code once, so the page must handle a missing record itself.
The stale build 404
With dynamicParams = false, the set of pages is frozen at build time, so content added afterward 404s until you rebuild. We hit this while checking which articles existed on 26 October. The count includes this article, which was written after the build.
The .next directory held a build from 24 October 20:36. Comparing its manifest with the content tree:
npm run check:static-params # → build prerendered 169 of 173 article routes; 4 newer: # nextjs-after-background-work, nextjs-generatestaticparams-dynamicparams, # nextjs-middleware-matcher, nextjs-route-handlers
After npm run build the same check reports 173 of 173, so the gap was staleness and nothing else. Before rebuilding, we served the old build and requested one old and one newer article:
npx next start --port 3417
curl -s -o /dev/null -w "%{http_code}" localhost:3417/next-js/nextjs-unstable-cache
# → 200
curl -s -o /dev/null -w "%{http_code}" localhost:3417/next-js/nextjs-route-handlers
# → 404
The second file exists in content/ and renders in next dev, yet the old build has no HTML for it. The server log recorded Error: Internal: NoFallbackError for the missing page. Nothing is broken in the page code; the build is simply older than the content.
This is the cost of dynamicParams = false: publishing is a deploy. Our pipeline already treats it that way, because every article is committed and built.
One source for the param list
The param list and the page body must read the same data source. If generateStaticParams reads a hand-kept array while the page reads files, you get two ways to drift: a slug in the array with no file renders a broken page, and a file with no entry 404s.
import { listPosts } from "@/lib/posts"; // your own loader
type PageProps = { params: Promise<{ slug: string }> };
export async function generateStaticParams() {
const posts = await listPosts();
return posts.map((post) => ({ slug: post.slug }));
}
export default async function Page({ params }: PageProps) {
const { slug } = await params;
return <h1>{slug}</h1>;
}
In Next.js 15 the page receives params as a Promise, so you await it. The values generateStaticParams returns are plain strings, not Promises. See Next.js 15 async request APIs for the migration.
If the loader reads files or a database, cache the parse so it runs once per process rather than once per page.
Which setting to pick
Pick the setting by who controls the list and when it changes.
dynamicParams = false: the list is complete at build time and publishing already triggers a build. Content sites, docs, and changelogs fit. You get immediate 404s and a fixed page set.- Default
truewithnotFound(): the data changes between deploys, such as a product catalog edited in a dashboard. Unknown ids render on demand, so the page looks the record up and callsnotFound()when it is absent. See error.tsx versus not-found.tsx. generateStaticParamsreturning[]: prerender nothing at build and render everything on demand. Use it when the list is too large to build, not as a default.
Best practices
- Derive params from the page's own loader. One source cannot disagree with itself.
- Keep
generateStaticParamsfree of request data. It runs at build time and has no cookies or headers. - Treat publishing as a build when
dynamicParamsis false. Add a rebuild step to the content workflow. - Test the 404 deliberately. Request one valid and one invalid URL against
next startbefore shipping. - Record the build you served. Compare the manifest with the content tree after each deploy.
Common mistakes
- Serving an old
.nextafter adding content. Symptom: new pages 404 onnext startbut work innext dev. Fix: run the build again. - Expecting
dynamicParams = falseto hide drafts. It only restricts the route to the list you return. Filter drafts in the loader. - Reading request data in
generateStaticParams. It runs at build time, so there is no request. Fix: move the lookup into the page. - Hand-writing a second slug list. Symptom: a slug in the list renders an empty page. Fix: map over the real data.
Conclusion
Return the full list from generateStaticParams, set dynamicParams = false when publishing already means a deploy, and keep the default with notFound() for data that changes between deploys. After any content change, rebuild before you judge a 404: our build was two days old and four articles short.
For the rest of the routing surface, read Next.js 15 Partial Prerendering and the Route Handlers guide.
Frequently asked questions
What does generateStaticParams do in Next.js?
What does dynamicParams = false do?
Why does my new page 404 after I add content?
Is generateStaticParams run on every request?
Should I use dynamicParams false or notFound()?
Muhammad Kashif
Founder and editor of Devventa, covering AI coding assistants, Next.js and the modern AI development stack.





