Most existing Next.js 13 to 15 apps use this model, and you will maintain or migrate them.
Apps that don't enable cacheComponents use the previous model, which you will meet in most existing codebases.
Routes are static or dynamic as a whole. At build time Next.js tries to prerender each route. Using a runtime API (cookies(), headers(), searchParams, connection()) or an uncached fetch marked cache: 'no-store' makes the entire route dynamic, rendered per request. Otherwise it is static, even if it fetched data at build time.
Caching is configured per request or per route:
fetch(url, { cache: 'force-cache' })caches a request; since Next.js 15 the default is not cached.fetch(url, { next: { revalidate: 3600, tags: ['posts'] } })caches with a lifetime and tags.unstable_cache(fn, keyParts, { revalidate, tags })caches non-fetch work like database queries.- Route segment config exports in a page or layout:
export const revalidate = 60(ISR: regenerate at most every 60 s),export const dynamic = 'force-dynamic'or'force-static',dynamicParams,fetchCache.
On-demand invalidation uses the same revalidatePath and revalidateTag(tag, profile) functions.
These segment options are removed when Cache Components is enabled. Migrating means replacing revalidate exports and fetch options with 'use cache' + cacheLife, and wrapping runtime access in Suspense. The official migration guide walks through it.
Example
// Previous model (cacheComponents not enabled)
// app/blog/page.tsx - regenerate this route at most once per hour (ISR)
export const revalidate = 3600
export default async function Blog() {
const res = await fetch('https://cms.example.com/posts', {
next: { revalidate: 3600, tags: ['posts'] }, // cached fetch with tags
})
const posts: { id: number; title: string }[] = await res.json()
return <ul>{posts.map((p) => <li key={p.id}>{p.title}</li>)}</ul>
}
// lib/users.ts - cache a database call
import { unstable_cache } from 'next/cache'
import { db } from '@/lib/db'
export const getUserCount = unstable_cache(
async () => db.user.count(),
['user-count'],
{ revalidate: 300, tags: ['users'] },
)
// app/account/page.tsx - reading cookies() makes the whole route dynamic
// export const dynamic = 'force-dynamic' // or make it explicitRemember
- Previous model: a route is entirely static or dynamic
- Cache via fetch options,
unstable_cacheand segment config likerevalidate - These options are removed under Cache Components; migrate to
'use cache'+cacheLife
Watch out for
- Copying
export const revalidateinto an app with Cache Components enabled, where it is no longer allowed.