A short cacheLife excludes a component from the static shell. If an entry would expire before it could be useful, Next.js turns it into a dynamic hole that resolves at request time instead of baking it into prerendered HTML. The result is still correct, it just stops being instant.
This article assumes Cache Components, where you cache with use cache and prerendering is partial by default.
The thresholds that matter
Two timing rules decide whether cached content can join the shell.
- A
revalidateof 0, or anexpireunder 5 minutes, excludes the entry from prerenders entirely. - A
staleunder 30 seconds also excludes it, because a prefetch would expire before the user could click. - A
staleof 30 seconds to 5 minutes still prerenders, but skips the route's App Shell.
In short, a short cacheLife pushes content from the shell into the request-time path.
Of the preset profiles, only seconds crosses these lines, because its expire is 1 minute. Everything else prerenders normally. If you need a fresh value per request, skip caching and read it behind Suspense instead.
Why the rules exist
A prerendered page is built ahead of time and stored. If a cached piece expires seconds after the build, storing it buys nothing, and the page would immediately serve stale HTML. Next.js responds by not storing it at all and leaving a Suspense fallback in the shell instead.
This is what keeps partial prerendering predictable: static content ships instantly, and anything too short-lived to store streams in later. These thresholds apply to time-based expiry only, while on-demand invalidation through tags clears the cache immediately.
The nested short-lived cache error
The thresholds also apply when one cached scope sits inside another. A short-lived cache nested in an outer use cache with no explicit cacheLife would silently shorten the outer lifetime, so Next.js throws during prerendering instead.
// app/components/short-lived-widget.tsx
import { cacheLife } from 'next/cache'
export async function ShortLivedWidget() {
'use cache'
cacheLife('seconds')
const data = await fetch('https://api.example.com/live')
const item = await data.json()
return <p>{item.value}</p>
}The widget caches for one second, which is fine on its own. The problem appears when another cache wraps it without stating its own lifetime:
// app/page.tsx
import { ShortLivedWidget } from '@/components/short-lived-widget'
export default async function Page() {
'use cache'
return (
<div>
<h1>Dashboard</h1>
<ShortLivedWidget />
</div>
)
}The outer cache has no cacheLife, so it would inherit a one second lifetime from the inner widget. Next.js refuses to prerender that and surfaces a build error.
How to fix it
Give the outer cache an explicit lifetime so its behavior no longer depends on what it renders.
// app/page.tsx
import { cacheLife } from 'next/cache'
import { ShortLivedWidget } from '@/components/short-lived-widget'
export default async function Page() {
'use cache'
cacheLife('default')
return (
<div>
<h1>Dashboard</h1>
<ShortLivedWidget />
</div>
)
}If you want the outer scope to stay short-lived, state that deliberately and wrap the widget in Suspense so a fallback shows while it loads. For the lifetime semantics, see cacheLife explained. For how fallbacks stream into the shell, see streaming in Next.js.
Rune AI
Key Insights
- revalidate of 0 or expire under 5 minutes blocks prerendering.
- stale under 30 seconds blocks prerendering and prefetching.
- stale between 30 seconds and 5 minutes skips the App Shell.
- Nested short-lived caches need an explicit outer cacheLife.
- Use Suspense to give the dynamic hole a fallback.
Frequently Asked Questions
Which cacheLife profile never joins the static shell?
How do I keep a short-lived cache while still prerendering?
Conclusion
Short lifetimes create dynamic holes by design. A stale under 30 seconds cannot be prefetched, and an expire under 5 minutes cannot be prerendered, so Next.js streams those parts at request time behind Suspense.
More in this topic
`generateMetadata` Explained with Real Examples
What generateMetadata does, when it runs, and how to use it for real routes: awaited params, deduplicated data fetching, extending parent metadata, and returning a 404 from metadata.
Canonical URLs in Next.js: `metadataBase`, `alternates.canonical`, and Dynamic Pages
How canonical URLs work in the Next.js App Router: setting metadataBase once, writing alternates.canonical per route, handling dynamic segments, and what happens when the base URL is missing.
Open Graph and Twitter Card Metadata in Next.js
How to write Open Graph and Twitter card metadata in the Next.js App Router: the openGraph and twitter fields, automatic card defaults, article tags, and image merge rules.