use cache + revalidateTag in Next.js 16
How to use the 'use cache' directive with cacheTag and revalidateTag for tag-based on-demand cache invalidation in Next.js 16.
'use cache' plus cacheTag plus revalidateTag is the Next.js 16 pattern for on-demand cache invalidation. Cache an expensive read once, tag it with a name, and invalidate every consumer atomically when the source changes. This replaces the older unstable_cache API and is the foundation for tag-driven ISR.
Tested on Next.js 16.0+.
When to Use This
- Caching a database read that powers many pages
- Caching a CMS fetch that updates via webhook
- Building a content collection that needs scheduled invalidation (e.g., from a cron)
- Sharing cached data between Server Components, Server Actions, and Route Handlers
Don't use this when the data is per-user (caches are global) or when the data must always be fresh (use no caching at all).
Code
// lib/blog.ts
import { cacheTag } from 'next/cache';
export async function getAllBlogs() {
'use cache';
cacheTag('blogs');
// expensive operation: read filesystem, parse frontmatter, etc.
const blogs = await readBlogsFromDisk();
return blogs;
}// app/api/revalidate-blogs/route.ts
import { revalidateTag } from 'next/cache';
export async function POST(request: Request) {
// ... auth check ...
revalidateTag('blogs');
return Response.json({ ok: true });
}The 'use cache' directive must be the first statement in the function body. cacheTag('blogs') registers the cached value under that tag — call it as many times as you want with different tag names to associate one cache entry with multiple tags.
Usage
A pattern I use on this site: a single cached blog loader feeding the list page, the slug page, the sitemap, and the RSS feed. One revalidateTag('blogs') call from a cron invalidates all four surfaces atomically.
// app/blogs/page.tsx
import { getAllBlogs } from '@/lib/blog';
export default async function BlogsPage() {
const blogs = await getAllBlogs(); // cached
return <BlogList blogs={blogs} />;
}// app/sitemap.ts
import { getAllBlogs } from '@/lib/blog';
export default async function sitemap() {
const blogs = await getAllBlogs(); // same cache, no extra fetch
return blogs.map((b) => ({ url: `/blogs/${b.slug}`, lastModified: b.updatedAt }));
}Both routes share the same cached value. When a webhook calls revalidateTag('blogs'), both regenerate on the next request.
Caveats
'use cache'must be the first line in the function. Even comments before it are fine, but no executable code. The directive is parsed at build time.- Cache keys include the function arguments.
getBlog('a')andgetBlog('b')are separate cache entries. Tagging applies to the function as a whole, not per-argument. revalidateTaginvalidates the data cache, not always the route cache. If a route handler hasexport const revalidate = N, you may also needrevalidatePathto flush the prerendered route shell. I learned this the hard way.- Cached values are serialized. Don't return Date objects or class instances — they lose their methods on cache restore. Stick to plain JSON.
- Don't mix
'use cache'withunstable_cachein the same project. Pick one. Mixing creates cache layer confusion that's hard to debug.
Related Snippets & Reading
- Scheduled Cron with curl — the cron that calls revalidateTag
- Auth-Gated proxy.ts — another Next.js 16 pattern
- Next.js Cache Components docs — official guide
Frequently Asked Questions
What does 'use cache' actually do in Next.js 16?
'use cache' marks a function (or file) as cacheable. Its return value is memoized across requests until invalidated. Combined with cacheTag, you can invalidate specific cached values by calling revalidateTag from anywhere — a webhook, a cron job, or a server action.
When should you use revalidateTag versus revalidatePath?
Use revalidateTag when you want to flush a specific data source (like 'all blog posts') across every page that consumes it. Use revalidatePath when you want to flush a specific URL or route segment. Tags scale better — one call invalidates everything tagged, no matter where it's used.