I want to understand why setting a placeholder in generateStaticParams works #92771
Replies: 5 comments 13 replies
|
Pre Cache Components, with an empty array from You'd then have to provide a set of params that hit the In a Cache Components, pages can both contain static and dynamic parts, but how does the prerendering step know what's being used in a given page, and that a static shell can be generated? Again, you provide a set of params that Next.js uses to try to generate a static shell, which can be placed on a CDN, while still being able to serve UI that depends on cookies/headers etc. When you by pass with a placeholder item, the build time is unable to discover any API access that would prevent creating a static shell. Of course, even if you provide one parameter, you can still have branches (like the placeholder one) that error during prerendering runtime, but the frameworks main intention is to validate that at least one value can complete. Moreover, we have an upcoming feature called |
This comment was marked as low quality.
This comment was marked as low quality.
|
I think the key distinction is that the placeholder is not what makes the route “lazy cached.” It mainly gives Cache Components a concrete parameter value to use during the build-time prerender/validation pass. With The important part is that So in your case, something like: export async function generateStaticParams() {
return [{ slug: ['__placeholder__'] }]
}is effectively giving the compiler a representative route so it can perform its prerender validation. The actual CMS slugs can still be discovered at runtime. The downside of the placeholder is specifically the quality of that validation. Next.js only executes the code paths that are reached with the placeholder value. If your application behaves differently for different real slugs—for example, one type of CMS page calls That's what the documentation means by:
So I wouldn't interpret the warning as “this will break lazy caching.” It's more like: you're intentionally giving up comprehensive build-time coverage in exchange for not having to know every CMS route at build time. And you're right about the runtime-slug argument: without This also explains the behavior you're seeing with There is also a useful related discussion in the Next.js repo showing that returning at least one value can change Cache Components behavior in ways that feel somewhat surprising: I'd therefore describe the placeholder as a workaround for the current build-time validation requirement, not as a magic switch for lazy caching. If your CMS route is intentionally “any slug can exist,” and you have tests/validation around the different CMS page types, the trade-off can be reasonable. But I would keep an eye on this behavior as Cache Components evolves, because relying on the placeholder specifically to bypass the empty-params restriction is not the same thing as Next.js officially guaranteeing that placeholder values are semantically meaningful. |
|
Am I correct that in 16.3 this changes? That we can now use I have a |
|
The placeholder works because with cacheComponents, generateStaticParams() is also used as a build-time validation signal, not only as a list of routes to prebuild. If you provide at least one param, Next.js can prerender that route variant during the build and validate that the route is compatible with Cache Components. Runtime params that were not returned by generateStaticParams() can still be rendered on demand and cached after the first request. Without generateStaticParams(), the value of params is unknown during prerendering, so Next treats it as runtime data. That is why accessing it can require a boundary. The downside of a placeholder is not that runtime slugs are inherently unsafe. The issue is that the build only validates the code path exercised by the placeholder. If a real slug causes a different branch to access runtime APIs such as cookies(), headers(), or searchParams incorrectly, that branch was never validated during the build. So the placeholder gives you the lazy static generation behavior you want, but with weaker build-time guarantees. If all real slugs follow the same rendering path, the practical risk is smaller, but that is why the docs call it a workaround rather than the preferred pattern. |


Uh oh!
There was an error while loading. Please reload this page.
https://nextjs.org/docs/messages/empty-generate-static-params#option-2-use-a-placeholder-param, this solution absolutely solves all my troubled with cacheComponents, but it states it should be avoided. Found this at #85807 (comment)
So I want to ask; why does this work at the first place?
With this, I can skip the
<Suspsense>around my{ slug } = await params(got only a[...slug]segment in my sites, it renders all CMS pages of all types, so I need it always).With this, I can see
.next/server/appfill up with.htmland.metafiles for all my pages. So this is actually what I want. All routes that get hit are hard cached when visited. Perfect.So why do I need this placeholder "hack" in the first place? Why doesn't it work without this?
And I don't understand the downsides. "minimal build-time validation and increases the risk of runtime errors for actual parameter values". Validation of what? What actual parameter values, slugs? Without a
generateStaticParams(I don't want to prebuild my entire site on build, I want it to cache lazy) I also risk runtime slugs right?All reactions