Do you know how to use NextJS caching system?
Next.js 15 introduced a more explicit and developer-friendly caching system.
But it's not automatic—caching only activates when you opt in using three mechanisms: enabling two flags (dynamicIO
, useCache
) in your config and adding the "use cache"
directive inside a file, a component or a function. This triple-lock prevents accidental caching and gives you fine-grained control over revalidation and tagging.
Why it matters
Without this opt-in model, stale content can easily sneak into your UI. Now, you’re in full control. You decide what gets cached, for how long, and what tags are used for easy invalidation.
Video: Is Next.js 15 any good? "use cache" API first look (8 min)
How to enable caching introduced by Next.js 15
To start using the new cache layer, you need to activate two experimental flags in next.config.js
, and then opt in at the file or function level using the "use cache"
directive:
// next.config.js
module.exports = {
experimental: {
dynamicIO: true,
useCache: true
}
}
Then, inside your route:
// app/products/page.js
'use cache'
export default async function ProductPage() {
const data = await fetch('/api/products', {
next: { tags: ['products'] }
})
return
}
Caching API options
Use the built-in functions below to fine-tune caching behavior:
cacheLife(seconds)
: Sets a time-to-live (TTL), similar to ISR’srevalidate
.cacheTag(tag)
: Associates a label (e.g.user-123
) to allow grouped invalidation.revalidateTag(tag)
: Clears all entries with that tag on next request—ideal for post-write updates.
cacheLife(3600)
cacheTag(`user-${id}`)
revalidateTag('products')
export async function POST() {
await updateDB()
revalidateTag('products')
}
Important changes with dynamicIO
When using dynamicIO
, some core Next.js behaviors change:
-
cookies()
andheaders()
become async functions:import { cookies } from 'next/headers' const token = (await cookies()).get('token')
-
Route files are dynamic by default. You must explicitly add:
export const dynamic = 'force-static'
if you want to guarantee build-time rendering.
Pro Patterns
Use hybrid caching for best results:
'use cache'
export default async function Dashboard() {
const staticData = await fetch('/api/metrics', { cacheLife: 3600 })
const liveData = await fetch('/api/activity', { cache: 'no-store' })
return
}
cacheTag(`user-${crypto.randomUUID()}`)
Figure: Good example - Mixing real-time and cached content gives you performance and freshness
Additional Notes
- Cache keys are based on build ID + args, so new props auto-generate new cache entries.
- The client router cache uses
staleTime: 0
by default—navigations recheck the server. - Deploys automatically clear cache, so no need for manual flushing.
You can learn more in depth about how to cache with Next.js in the official documentation.
Common Pitfalls
Avoid caching components that depend on non-serializable props (e.g. children
):
'use cache'
function SafeCache({ children }) {
return <section>{children}</section>
}
Debugging Tips
To inspect what was cached or missed:
NEXTJS_CACHE_ANALYTICS=1 next build
This emits a hit/miss report for each route to the console, giving visibility into caching behavior.