Do you know how to use NextJS caching system?
Updated by Gilles Pothieu [SSW] 3 months ago. See history
123
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. <youtubeEmbed url="https://www.youtube.com/watch?v=xWkozeculPo" description="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: ```javascript // next.config.js module.exports = { experimental: { dynamicIO: true, useCache: true } } ``` Then, inside your route: ```javascript // 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’s `revalidate`. - **`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. ```javascript 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: 1. `cookies()` and `headers()` become async functions: ```javascript import { cookies } from 'next/headers' const token = (await cookies()).get('token') ``` 2. Route files are dynamic by default. You must explicitly add: ```javascript export const dynamic = 'force-static' ``` if you want to guarantee build-time rendering. ## Pro Patterns Use hybrid caching for best results: ```javascript '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()}`) ``` <figureEmbed figureEmbed={{ preset: "goodExample", figure: 'Figure: Good example - Mixing real-time and cached content gives you performance *and* freshness', shouldDisplay: true } } /> ## 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](https://nextjs.org/docs/app/building-your-application/caching). ## Common Pitfalls Avoid caching components that depend on non-serializable props (e.g. `children`): ```javascript 'use cache' function SafeCache({ children }) { return <section>{children}</section> } ``` ## Debugging Tips To inspect what was cached or missed: ```bash NEXTJS_CACHE_ANALYTICS=1 next build ``` This emits a hit/miss report for each route to the console, giving visibility into caching behavior.
acknowledgements
related rules