cacheLife()
Set how long a 'use cache' scope stays fresh, stale and valid, by profile or inline object.
cacheLife(profile: string | { stale?: number; revalidate?: number; expire?: number }): voidParameters
| Parameter | Type | Description |
|---|---|---|
$profile | string | { stale?: number; revalidate?: number; expire?: number } | A built-in profile name (default, seconds, minutes, hours, days, weeks, max), a custom profile defined under cacheLife in next.config, or an inline object of times in seconds. Omitted properties inherit from the default profile, and cacheLife({}) applies the default profile outright. |
$profile.staleoptional | number= 300 | Seconds the client router may serve cached content without checking the server. Defaults to the default profile's 5 minutes; the router enforces a 30-second minimum so prefetched links stay usable. |
$profile.revalidateoptional | number= 900 | Seconds after which the server regenerates the entry in the background, serving the cached version meanwhile. Defaults to the default profile's 15 minutes. |
$profile.expireoptional | number | Maximum seconds before the server must regenerate synchronously on the next request. Must be longer than revalidate. Defaults to the default profile's value, which never expires. |
Return value
voidNothing. The enclosing cache scope adopts the given lifetime.
Errors
Throws when called at module scope or outside a cache directive scope, when expire is shorter than revalidate, and during prerendering when a short-lived nested cache is used from an outer 'use cache' that sets no explicit cacheLife.
cacheLife answers "how long?" for a 'use cache' scope. The three numbers are separate audiences: stale is the client router, revalidate is the background refresh on the server, and expire is the point where a request must wait for fresh content. Requires cacheComponents in next.config.
Set it every time
Omitting it applies the default profile, and the lifetime stops being visible at the call site. Worse, an outer scope without an explicit cacheLife inherits downward: a nested cache with a shorter life drags the outer one down, while a longer one cannot extend it past the default. With an explicit profile you can read one function and know its behavior without tracing the tree.
What trips people up
- Short lifetimes change where content is served from. A
revalidateof 0 or anexpireunder 5 minutes drops the scope out of prerendering into a request-time hole; astaleunder 30 seconds does the same, because a prefetch would expire before the click. Of the presets onlysecondscrosses a threshold. - Only one call may execute per invocation — branch it, do not stack it.
- Redefining a built-in name in
next.configis supported, but thencacheLife('hours')no longer means hours to the next reader.
Example
import { cacheLife } from 'next/cache'
export default async function BlogPost({
params,
}: {
params: Promise<{ slug: string }>
}) {
'use cache'
cacheLife('days') // Blog posts update daily
const { slug } = await params
const post = await fetchBlogPost(slug)
return <article>{post.content}</article>
}When to use it
- Give blog pages a daily refresh with cacheLife('days') while product pages use 'hours'.
- Cache a draft-or-missing record briefly and a published one for days, by branching the call.
- Read a revalidate window out of CMS data and pass it as an inline profile object.
Frequently asked questions
What is the difference between stale, revalidate and expire?
stale is how long the client router serves its cached copy without contacting the server, revalidate is how often the server regenerates the entry in the background while still serving the cached one, and expire is the ceiling after which the next request waits for fresh content. expire must be longer than revalidate or Next.js raises an error.
What happens if I don't call cacheLife?
The default profile applies: 5 minutes stale, 15 minutes revalidate, never expires. The lifetime also stops being explicit, so a nested cache with a shorter life can shorten the outer scope, and nesting a short-lived cache inside one with no explicit cacheLife fails the build during prerendering.
Why does my cached page render at request time instead of being prerendered?
Its lifetime crossed a prerender threshold: a revalidate of 0 or an expire under 5 minutes turns the scope into a request-time hole, and a stale under 30 seconds does too because a prefetch would expire before the user could click. Among the presets, only 'seconds' falls below one.
More examples
Built-in profiles
Lives at lib/data.ts. The table is the whole decision: pick the row whose revalidate matches how often the content actually changes. 'seconds' is the one preset whose expire (1 minute) is short enough to exclude it from prerenders, so it always resolves at request time.
// Profile stale revalidate expire
// default 5 minutes 15 minutes never
// seconds 30 seconds 1 second 1 minute
// minutes 5 minutes 1 minute 1 hour
// hours 5 minutes 1 hour 1 day
// days 5 minutes 1 day 1 week
// weeks 5 minutes 1 week 30 days
// max 5 minutes 30 days 1 year
import { cacheLife } from 'next/cache'
export async function getSettings() {
'use cache'
cacheLife('max') // Settings rarely change
return fetchSettings()
}
export async function getRealtimeStats() {
'use cache'
cacheLife('seconds') // Stats update constantly
return fetchStats()
}Custom and inline profiles
Lives at next.config.ts. Naming a profile once means cacheLife('editorial') reads the same everywhere and changes in one place. Any property you leave out inherits from the default profile — the same rule applies to inline objects passed straight to cacheLife().
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
cacheComponents: true,
cacheLife: {
editorial: {
stale: 600, // 10 minutes
revalidate: 3600, // 1 hour
expire: 86400, // 1 day
},
},
}
export default nextConfigBranching on the data
Lives at lib/posts.ts. Two calls appear in the source but only one runs per invocation, which is the rule. A missing record gets a short life because it is likely to appear soon; a published one gets a long life plus a tag for on-demand invalidation.
import { cacheLife, cacheTag } from 'next/cache'
export async function getPostContent(slug: string) {
'use cache'
const post = await fetchPost(slug)
cacheTag(`post-${slug}`)
if (!post) {
// Might be unpublished; cache briefly to spare the database
cacheLife('minutes')
return null
}
cacheLife('days')
return post.data
}
Discussion