React

revalidateTag: invalidating the right data

A tag links a fetch to its invalidation: revalidateTag clears every entry with that tag, not one page. Where to call it and what breaks.

Key points

  • A tag is a label on a fetch: revalidateTag hits every entry carrying it.
  • Call it from a Server Action or a Route Handler, never during render.
  • Since Next.js 15 fetch is not cached by default: no cache means no tag effect.
  • Across instances the cache is per process, so you need a shared handler.

A tag is a label you attach to a fetch, and revalidateTag invalidates every entry carrying that label. It is not a page refresh: it is a targeted deletion that applies to the whole application.

The full loop

// lib/products.ts
export async function getProducts() {
  const res = await fetch("https://api.example.com/products", {
    next: { tags: ["products"], revalidate: 3600 },
  });
  return res.json();
}

The label lives in the next option of the fetch, not in the response. Every page that calls getProducts shares one cache entry and one tag: a single invalidation covers them all.

Where to call it

// app/api/revalidate/route.ts
import { revalidateTag } from "next/cache";
import { NextResponse } from "next/server";

const TAGS = new Set(["products", "orders"]);

export async function POST(request: Request) {
  const secret = request.headers.get("x-revalidate-secret");
  if (secret !== process.env.REVALIDATE_SECRET) {
    return NextResponse.json({ ok: false }, { status: 401 });
  }
  const tag = new URL(request.url).searchParams.get("tag") ?? "";
  if (!TAGS.has(tag)) {
    return NextResponse.json({ ok: false, reason: "unknown tag" }, { status: 400 });
  }
  revalidateTag(tag);
  return NextResponse.json({ ok: true, tag });
}

The secret and the allowlist are not ceremony: whoever calls that endpoint can empty the cache. With few tags, a closed list costs two lines and removes the attack surface.

The call

curl -X POST "http://localhost:3000/api/revalidate?tag=products" \
  -H "x-revalidate-secret: $REVALIDATE_SECRET"

Tag, path or time

ToolWhat it invalidatesWhen to use it
revalidateTagEvery fetch with that tagThe datum changes and several pages read it
revalidatePathThe cache of one pathA single page changed
revalidate: nThe single fetch after n secondsThe datum ages well on its own
router.refresh()The client, not the cacheYou want a server read after a mutation

The usual mistakes

  • Calling revalidateTag during the render of a server component: the runtime rejects it.
  • Putting the tag on the response instead of the fetch: the label belongs in the next option.
  • Assuming the tag knows the language: products covers every locale. If the price changes only in Italy, use products-it.
  • Forgetting that Next.js 15 does not cache fetch by default: without next.revalidate or cache: "force-cache" the tag has no effect.

The cases where stale data shows immediately are data tables, stats and a table block.

The cache levels are in the four cache layers; HTTP behaviour in stale-while-revalidate and cache headers. Comparison: Next.js vs Vite.

Known limitation: the Data Cache lives in the process that ran the fetch. With two instances, invalidating on one leaves the other untouched: you need a shared cache handler or a short TTL.

Frequently asked questions

Are revalidateTag and revalidatePath equivalent?

No. The tag invalidates every fetch carrying it, the path invalidates the cache of one route. Use the tag when the same datum feeds several pages.

Can I call it from a client component?

No. The function runs on the server: call it from a Server Action or a Route Handler, then refresh the client with router.refresh.

Why does the tag seem to do nothing?

Because the fetch is not cached. Since Next.js 15 caching is opt-in: add next.revalidate or cache force-cache to the fetch.

How many tags do I need?

One per entity that changes together. If two data sets always update at the same moment, one tag avoids double invalidation.

Is the tag per language?

Not by default. If the content changes per locale, put the locale in the name, for example products-it.