revalidateTag()

Mark every cache entry carrying a tag as stale, across all routes that use it.

Signature
revalidateTag(tag: string, profile: string | { expire?: number }): void

Parameters

ParameterTypeDescription
$tagstringThe cache tag to invalidate. Case-sensitive, max 256 characters — a longer tag is never assigned to cached data in the first place, so revalidating it does nothing.
$profilestring | { expire?: number }How long stale content may still be served while revalidation runs. 'max' (recommended) gives a one-year window so requests are always served instantly. Any other built-in or custom cacheLife profile works and only its expire is read, as does an inline object such as { expire: 0 }, which serves nothing stale and makes the next request block.

Return value

voidNothing. Tagged entries are marked stale and revalidate on the next request for that data.

Errors

Throws when called in a Client Component or in Proxy, because it only works in server environments.

Changelog

  • PHP v16The single-argument form revalidateTag(tag) is deprecated. It still works if the TypeScript error is suppressed and behaves like { expire: 0 }, but may be removed; migrate to updateTag in Server Actions, or pass profile="max".

revalidateTag invalidates data by label rather than by location. Every entry carrying the tag — from any fetch with next.tags, or any 'use cache' scope that called cacheTag — is marked stale at once, no matter which routes read it.

Stale-while-revalidate is the point

The call marks; it does not rebuild. The next request for that data starts the revalidation and is served the stale copy while it runs. The second argument decides how long that grace window lasts, which is really a decision about when correctness starts to matter more than speed. 'max' is a one-year window, so readers are never blocked — the right default for blog posts, catalogs, and docs. { expire: 0 } serves nothing stale, so the next request is a blocking miss.

What trips people up

  • Nothing revalidates until a request arrives. Pages refresh as they are visited, not all at once when the webhook fires.
  • The one-argument form is deprecated and quietly means { expire: 0 } — the slow behavior people then blame on tags.
  • Inside a Server Action where the user must immediately see their own write, updateTag is the correct tool; revalidateTag is for webhooks, Route Handlers, and background invalidation.

Example

Example · typescript
import type { NextRequest } from 'next/server'
import { revalidateTag } from 'next/cache'

export async function GET(request: NextRequest) {
  const tag = request.nextUrl.searchParams.get('tag')

  if (tag) {
    revalidateTag(tag, 'max')
    return Response.json({ revalidated: true, now: Date.now() })
  }

  return Response.json({
    revalidated: false,
    now: Date.now(),
    message: 'Missing tag to revalidate',
  })
}

When to use it

  • Give a CMS a webhook endpoint that invalidates the 'posts' tag when an editor publishes.
  • Invalidate a product catalog tag from a Route Handler after an inventory sync job runs.
  • Expire data immediately from outside a Server Action with revalidateTag(tag, { expire: 0 }).

Frequently asked questions

What is the difference between revalidateTag and updateTag?

revalidateTag marks tagged data stale and (with a profile like 'max') keeps serving the cached copy while fresh data loads in the background; updateTag expires the data so the next read waits for fresh content. revalidateTag works in Server Actions and Route Handlers, updateTag only in Server Actions. Use updateTag for read-your-own-writes, revalidateTag for webhooks and background invalidation.

Why does revalidateTag now require a second argument?

The single-argument form is deprecated because its behavior — expire immediately, block the next reader — is rarely what people want from a webhook. Passing 'max' opts into stale-while-revalidate, which is the recommended default; passing { expire: 0 } reproduces the old behavior explicitly.

Does revalidateTag rebuild all my pages immediately?

No. It marks entries stale, and revalidation is triggered by the next request for that data, so pages refresh as they are visited rather than all at once. That also means a tag used by a page nobody visits costs nothing.

More examples

Tagging data so it can be invalidated

Lives at lib/posts.ts. Both functions attach the same 'posts' tag by different means — cacheTag inside a 'use cache' scope, and next.tags on a plain fetch. A single revalidateTag('posts', 'max') marks both, which is what makes tags scale better than paths.

Example · typescript
import { cacheTag, cacheLife } from 'next/cache'

export async function getPosts() {
  'use cache'
  cacheTag('posts')
  cacheLife('max')

  const res = await fetch('https://api.example.com/posts')
  return res.json()
}

export async function getLatestPosts() {
  // The fetch cache takes tags directly
  const res = await fetch('https://api.example.com/posts?limit=5', {
    next: { tags: ['posts'] },
  })
  return res.json()
}

Long cache plus on-demand invalidation

Contrasts the two windows. 'cms-content' readers never wait — they get last-known-good content while a background revalidation runs. 'pricing' readers do wait, which is the trade you make when serving a stale number is worse than serving a slow one.

Example · typescript
'use server'

import { revalidateTag } from 'next/cache'

export async function syncFromCms() {
  await pullLatestContent()

  // Serve the old copy while the new one is built
  revalidateTag('cms-content', 'max')

  // Compare: block the next reader until fresh data is ready
  revalidateTag('pricing', { expire: 0 })
}

The error you get in a Route Handler

Lives at app/api/posts/route.ts. It shows the boundary between the two functions: updateTag is Server-Action-only, so any webhook or API endpoint that needs to invalidate a tag must use revalidateTag.

Example · typescript
import { revalidateTag, updateTag } from 'next/cache'

export async function POST() {
  // Throws: updateTag can only be called from within a Server Action
  // updateTag('posts')

  // Use revalidateTag here instead
  revalidateTag('posts', 'max')

  return Response.json({ ok: true })
}

Discussion

  • Be the first to comment on this lesson.