Cache inheritance and conditional static generation in Next.js?
This post might have stale content, as HEAD is 253 commits ahead.
In Next.js 16 with cache components:
- If I have a component with 'use cache: private' and it renders children that have 'use cache' (public), what happens? Does the child inherit private caching or stay public?
- How do I conditionally do static generation for only some pages? Like generateStaticParams returns pages, but I want some routes to NOT be statically generated at all (always dynamic).
Perfect! Now I have all the information I need. Let me provide a comprehensive answer.
##Answer
Based on my analysis of the Next.js 16 source code, here are the answers to your questions:
###1. Cache Scope Inheritance: Private vs Public
The child DOES NOT inherit the parent's private scope. Instead, you will get a compile/runtime error.
Looking at the validation logic in use-cache-wrapper.ts (lines 914-921):
case 'cache': {
throw wrapAsInvalidDynamicUsageError(
new Error(
`${expression} must not be used within "use cache". It can only be nested inside of another ${expression}.`
),
workStore
)
}
The rule is:
- ✅
'use cache: private'can be nested inside another'use cache: private' - ❌
'use cache'(public) cannot be nested inside'use cache'(public) - ❌
'use cache: private'cannot be nested inside'use cache'(public)
Example - This will error:
// ❌ ERROR: "use cache: private" must not be used within "use cache".
// It can only be nested inside of another "use cache: private".
export default async function Page() {
'use cache' // public cache
return <Child />
}
async function Child() {
'use cache: private' // ❌ Cannot nest private in public
return <div>...</div>
}
Example - This will work:
// ✅ OK: private cache nested in private cache
async function Parent() {
'use cache: private'
return <Child />
}
async function Child() {
'use cache: private' // ✅ OK to nest private in private
return <div>...</div>
}
###2. Conditional Static Generation - Make Some Routes Always Dynamic
You have two approaches:
####Option A: Use export const dynamic = 'force-dynamic' (Recommended)
This completely opts a route out of static generation, even if generateStaticParams returns values for it:
// app/blog/[slug]/page.tsx
export const dynamic = 'force-dynamic'
export async function generateStaticParams() {
return [
{ slug: 'post-1' },
{ slug: 'post-2' },
{ slug: 'post-3' },
]
}
export default async function Page({ params }: { params: { slug: string } }) {
// This route is ALWAYS dynamic, never prerendered
// Even though generateStaticParams has values
const data = await fetch(`/api/posts/${params.slug}`)
return <div>{data}</div>
}
Use force-dynamic when:
- Routes need to be generated on-demand (e.g., user-specific content)
- You have
generateStaticParamsfor fallback/prefetching but don't want build-time prerendering - Routes depend on dynamic request data (cookies, headers, search params)
####Option B: Use dynamicParams = false + selective generateStaticParams
Return only the routes you want prerendered from generateStaticParams:
// app/blog/[slug]/page.tsx
export const dynamicParams = false // Block routes not in generateStaticParams
export async function generateStaticParams() {
// Only these specific slugs are prerendered
return [
{ slug: 'featured-post-1' },
{ slug: 'featured-post-2' },
]
// All other routes (e.g., /blog/random-slug) → 404
}
export default async function Page({ params }: { params: { slug: string } }) {
const data = await fetch(`/api/posts/${params.slug}`)
return <div>{data}</div>
}
Use dynamicParams = false when:
- You want a whitelist of prerendered routes
- Other routes should 404 instead of being generated on-demand
- You have a finite, known set of valid routes
###Summary Table
| Scenario | Solution | Result |
|---|---|---|
| Want some routes prerendered, others always dynamic | export const dynamic = 'force-dynamic' | Routes never prerendered, always SSR |
| Want only specific routes prerendered | export const dynamicParams = false + return specific slugs from generateStaticParams | Only those routes prerendered; others 404 |
Child has 'use cache', parent has 'use cache: private' | Not allowed | Runtime error |
Child has 'use cache: private', parent has 'use cache: private' | Allowed | Works correctly |
Source files:
- Cache scope rules:
packages/next/src/server/use-cache/use-cache-wrapper.ts(lines 905-925) - Dynamic config:
packages/next/src/server/typescript/rules/config.ts(lines 100-130) - Tests:
test/e2e/app-dir/cache-components-errors/
END OF POST