"use cache" with cacheTag, revalidateTag + refresh() for invalidation, granular tags.
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 recommended approach for this project:
data/queries/, call in Server Components. Use React's cache() for request deduplication.data/actions/ with "use server". Invalidate with revalidateTag(tag, 'max') + refresh()."use cache" to functions, components, or pages you want to cache across requests.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'
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.
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(
| Function | Purpose |
|---|---|
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.
Tag individual items for precise invalidation:
export const getPublishedPostBySlug = cache(async (slug: string) => {
'use cache';
cacheTag(`post-${slug}`);
returnWhen 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.
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.