Caching Without Cache Components: `fetch` Options and Segment Configs

The previous caching model, for apps without cacheComponents. Learn fetch cache options, unstable_cache, and the route segment configs that control it.

7 min read

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.tsxApp.tsx
// 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:

typescripttypescript
// 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.

ConfigEffect
dynamicforce-dynamic, force-static, error, or the auto default
revalidateA default lifetime in seconds for the segment
fetchCacheOverrides 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.

typescripttypescript
// 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

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.
RunePowered by Rune AI

Frequently Asked Questions

Does fetch cache by default in this model?

No. fetch requests are uncached by default. Pass cache: 'force-cache' to cache an individual request.

When should I use this model instead of Cache Components?

Only when you have not enabled cacheComponents. New projects should prefer Cache Components, and this model is for apps that have not migrated yet.

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.