The "use cache" Directive

Caching eliminates in-between states entirely—if content is pre-rendered, there's nothing to wait for. Next.js 16 introduces the "use cache" directive for fine-grained, explicit caching control.

With cacheComponents: true in your Next.js config, data fetching is dynamic by default. This is the opposite of the previous implicit caching behavior. You now opt into caching explicitly with "use cache".

The Development Flow

The recommended approach for this project:

  • Fetching data — Create queries in data/queries/, call in Server Components. Use React's cache() for request deduplication.
  • Mutating data — Create Server Functions in data/actions/ with "use server". Invalidate with revalidateTag(tag, 'max') + refresh().
  • Caching — Add "use cache" to functions, components, or pages you want to cache across requests.

Basic Usage

Add the directive at the top of a function, then tag the cache for invalidation:

import { cacheTag } from 'next/cache'; import { cache } from 'react'; export const getPublishedPosts = cache(async () => { 'use cache'; cacheTag('posts'); return await prisma.post.findMany({ where: { published: true, archived: false }, orderBy: { createdAt: 'desc' }, }); });

The cache() wrapper from React deduplicates calls within a single render. The "use cache" directive caches the result across requests. Together, they eliminate both redundant queries within a request and redundant work across requests.

Cache Invalidation

After mutations, you need to invalidate the cache and update the current user's view:

import { refresh, revalidateTag } from 'next/cache'; export async function createPost(formData: FormData) { 'use server'; await prisma.post.create({ data }); revalidateTag('posts', 'max'); // Mark stale, revalidate in background refresh(); // Force immediate re-render }
FunctionPurpose
revalidateTag(tag, 'max')Marks cache stale, triggers background revalidation
updateTag(tag)Marks cache stale, revalidates immediately (blocking)
refresh()Forces immediate client re-render with fresh data

The 'max' profile serves stale content while revalidating—other users get fast responses while fresh data generates. Use updateTag when you need the mutation to block until fresh data is ready.

Granular Cache Tags

Tag individual items for precise invalidation:

export const getPublishedPostBySlug = cache(async (slug: string) => { 'use cache'; cacheTag(`post-${slug}`); return await prisma.post.findUnique({ where: { slug, published: true }, }); });

When updating a post:

revalidateTag('posts', 'max'); // Invalidate the list revalidateTag(`post-${slug}`, 'max'); // Invalidate this specific post refresh();

This ensures both the post list and the individual post's detail page update.

Push Dynamic Data Deep

To maximize cacheable content, push dynamic data access as deep as possible in the component tree. Components that read searchParams, cookies(), or headers() become dynamic—wrap them in <Suspense> so static parent content can render immediately.

October 23, 2025Updated February 24, 2026447 words