Skip to content
Now accepting new projects — limited slots available. Get started →
Patterns · Updated Aug 5, 2026

What is Cache Invalidation?

Cache invalidation is a pattern that removes or replaces stale cached data when the source of truth changes.

What is Cache Invalidation?

Cache invalidation is the process of marking cached data as stale or explicitly purging it so that subsequent requests fetch a fresh copy from the origin. Phil Karlton famously called it one of the two hard problems in computer science (along with naming things), and after shipping 50+ projects we can confirm he wasn't exaggerating. Invalidation strategies fall into three broad buckets: time-based (TTL expiry), event-driven (on-demand purge triggered by a webhook or CMS publish), and tag-based (grouping cached entries under a shared label so you can purge them as a set). Getting invalidation wrong means users see stale prices, old headlines, or broken layouts. Getting it right means you serve pages from edge cache at sub-50ms TTFB while still reflecting content changes within seconds. Next.js 14+ introduced revalidateTag() and revalidatePath() APIs that make on-demand invalidation a first-class pattern for App Router projects.

How it works

Every cache entry carries metadata: a key (usually the URL or a computed hash), a freshness indicator (TTL or ETag), and optionally one or more tags. On a cache hit, the server checks if the entry's still fresh. If the TTL expired, it either serves stale-while-revalidate (fetching a fresh copy in the background) or blocks until the origin responds.

On-demand invalidation skips the TTL clock entirely. A CMS webhook fires on publish, your API route receives it, and you call the invalidation function:

// app/api/revalidate/route.ts (Next.js 14+)
import { revalidateTag } from 'next/cache';
import { NextRequest, NextResponse } from 'next/server';

export async function POST(req: NextRequest) {
  const secret = req.headers.get('x-revalidate-secret');
  if (secret !== process.env.REVALIDATE_SECRET) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  const { tag } = await req.json();
  revalidateTag(tag); // purges all fetch() calls tagged with this value
  return NextResponse.json({ revalidated: true, tag });
}

On the data-fetching side, you tag your requests:

const posts = await fetch('https://cms.example.com/api/posts', {
  next: { tags: ['posts'] },
});

When the webhook hits /api/revalidate with { "tag": "posts" }, every cached response tagged posts is purged across the deployment. Vercel propagates this across its edge network globally in under 300ms in most cases. Cloudflare's Purge by Tag API and Fastly's surrogate keys follow the same concept at the CDN layer.

When to use it

We've shipped cache invalidation on everything from e-commerce to SaaS marketing sites. It matters most when you're caching aggressively but your content changes unpredictably.

Use on-demand invalidation when:

  • CMS-driven content (blog posts, product pages) needs to appear live within seconds of publish
  • Pricing or inventory data changes and stale values cost real money
  • You're running ISR (Incremental Static Regeneration) and don't want to wait for a TTL window
  • Multiple pages share the same underlying data (tag-based purge is far cleaner than path-by-path)

Stick with TTL-based invalidation when:

  • Data changes on a predictable schedule (e.g., daily exchange rates)
  • Staleness is acceptable for minutes or hours (analytics dashboards, leaderboards)
  • You don't control the origin and can't set up webhooks

Skip caching entirely when:

  • The response is user-specific and authenticated (shopping carts, account settings)
  • Data changes on every request (real-time collaboration feeds)

Cache Invalidation vs alternatives

Strategy Freshness guarantee Complexity Best for
TTL expiry Bounded by TTL window Low Predictable update cycles
Stale-while-revalidate Serves stale, refreshes async Low-medium Traffic-heavy pages tolerant of brief staleness
On-demand (path) Near-instant per path Medium Known URL structures
On-demand (tag) Near-instant per data group Medium CMS-driven sites with shared data across many pages
No cache / SSR Always fresh Low config, high compute Personalized or real-time data

Our preferred stack is tag-based on-demand invalidation in Next.js App Router, triggered by Sanity or Contentful webhooks. Edge-cached performance with publish-time freshness. That's the sweet spot for the marketing and e-commerce sites we build.

Real-world example

On a recent e-commerce build with ~8,000 product pages, we cached every PDP at the edge with a product-{sku} tag plus a shared global-nav tag. When a merchandiser updated a product in Sanity, a webhook hit our /api/revalidate endpoint and purged only that SKU's tag. When the nav menu changed, we purged global-nav, which invalidated all 8,000 pages in one call. Average TTFB stayed under 45ms from Vercel's edge, and content updates reflected live within 2 seconds of the CMS publish event. Before switching to tag-based invalidation, the team was using a 60-second ISR revalidation window, which meant stale prices could linger for up to a minute—a real problem during flash sales.

Frequently asked questions about Cache Invalidation

Is cache invalidation the same as cache expiration?
No. Cache expiration is passive — the entry sits in cache until its TTL runs out, then it's considered stale. Cache invalidation is active — you explicitly tell the cache to drop or refresh an entry before the TTL expires. Expiration is a subset of invalidation strategy. In practice, most production systems use both: a generous TTL as a safety net (say, 1 hour) combined with on-demand invalidation for real-time freshness when the origin data changes. This way, even if your webhook fails, the worst case is content that's stale for the TTL duration, not forever.
When did on-demand cache invalidation become standard in Next.js?
Next.js introduced `revalidatePath()` and `revalidateTag()` as stable APIs in Next.js 14, released in October 2023. Before that, ISR relied solely on TTL-based revalidation (the `revalidate` option in `getStaticProps`), and on-demand ISR was available as an unstable API starting in Next.js 12.1 (February 2022). The Next.js 14 release made tag-based invalidation a first-class pattern in the App Router, and by 2025 it became the default approach for most teams doing content-driven sites on Vercel.
What's the alternative to cache invalidation?
The main alternative is to skip caching dynamic data altogether and render on every request (pure SSR). This guarantees freshness but costs you compute time and higher TTFB — typically 200-800ms from origin vs. 20-50ms from edge cache. Another approach is stale-while-revalidate, where you always serve the cached version instantly and refresh in the background. SWR is simpler to set up but doesn't give you the instant-freshness guarantee that on-demand invalidation does. For client-side data, libraries like TanStack Query handle cache invalidation through mutation callbacks, which is a solid pattern for interactive UIs.
How do you debug cache invalidation issues in production?
Start with response headers. Check `x-vercel-cache` (or `cf-cache-status` on Cloudflare) to see if you're getting a HIT, MISS, or STALE. If you're getting HITs after a purge, the invalidation likely didn't propagate — verify your webhook is actually firing (check CMS delivery logs) and that your revalidation endpoint returns a 200. Next.js also logs revalidation events in the function logs on Vercel. We add structured logging to our `/api/revalidate` routes so we can search by tag name and timestamp. A common gotcha: forgetting to tag a `fetch()` call, which means `revalidateTag()` silently does nothing for that request.
Get in touch

Let's build
something together.

Whether it's a migration, a new build, or an SEO challenge — the Social Animal team would love to hear from you.

Get in touch →