Caching without Cache Components means fetch requests are uncached by default. This is the previous model, used when cacheComponents is not enabled, and you opt in with fetch options, unstable_cache, and route segment configs. If you are on Cache Components, use the directive instead.
fetch cache options
A single request opts in with cache: 'force-cache', and you tune its lifetime and tags through the next options. The revalidate value is in seconds, and tags feed on-demand invalidation:
// app/page.tsx
export default async function Page() {
const res = await fetch('https://api.example.com/posts', {
cache: 'force-cache',
next: { revalidate: 3600, tags: ['posts'] },
})
const posts = await res.json()
return <ul>{posts.map((post) => <li key={post.id}>{post.title}</li>)}</ul>
}The response is stored and reused for up to an hour, and the posts tag lets you invalidate it later. The page renders once and stays static until the hour passes or the tag is invalidated. See does Next.js fetch cache by default for the version history behind this default.
unstable_cache for non-fetch work
fetch options only cover fetch. For database queries and other async work, wrap the function in unstable_cache:
// app/lib/posts.ts
import { unstable_cache } from 'next/cache'
export const getPosts = unstable_cache(
async () => {
const res = await fetch('https://api.example.com/posts')
return res.json()
},
['posts'],
{ tags: ['posts'], revalidate: 3600 }
)The key-parts array and options object work the same as they always have, and the entry persists across deployments and serverless instances. Use it for ORM and database calls, which fetch options cannot see.
Memoization is separate from caching here. A GET fetch with the same URL and options is deduplicated within one render pass, and React cache() does the same for any async function, which helps when a layout and a page both query the same data.
Route segment config
Three exports change caching for a whole layout, page, or route handler.
| Config | Effect |
|---|---|
| dynamic | force-dynamic, force-static, error, or the auto default |
| revalidate | A default lifetime in seconds for the segment |
| fetchCache | Overrides the cache option of every fetch in the segment |
The dynamic export accepts force-dynamic, force-static, error, or auto. The lowest revalidate across a route's segments wins, so a child can make the whole route revalidate more often.
These belong to this pre-Cache-Components model only. With Cache Components enabled they error, and you use use cache and cacheLife instead. That split is why the model matters: the same config means different things depending on which one you are running.
On-demand revalidation
Tag data with next.tags or unstable_cache tags, then invalidate after a mutation.
// app/actions.ts
'use server'
import { revalidateTag, revalidatePath } from 'next/cache'
export async function refreshPosts() {
revalidateTag('posts', 'max')
revalidatePath('/blog')
}revalidateTag clears every entry with the tag, and revalidatePath clears one route. Both run on the server and are the bridge between a mutation and the next page view. React cache() also plays a role here for deduplicating calls within a request, as covered in use cache vs React cache vs unstable_cache.
Rune AI
Key Insights
- fetch is uncached by default in the previous model.
- cache: 'force-cache' opts a single request in.
- unstable_cache covers non-fetch async work.
- dynamic, revalidate, and fetchCache control whole routes.
- revalidateTag and revalidatePath handle on-demand invalidation.
Frequently Asked Questions
Does fetch cache by default in this model?
When should I use this model instead of Cache Components?
Conclusion
Without Cache Components, caching is still opt-in. Use cache: 'force-cache' on fetch, unstable_cache for other async work, and route segment configs for whole-route control. On-demand invalidation works through tags and paths.
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.