Skip to content
brainNotFound

Build Systems/Rendering

ISR and cache invalidation that actually invalidates

Tag granularity, webhook payloads and how to prove a publish reached production in under ten seconds.

intermediate20 min
On this page
  1. Tag at three levels, not one
  2. The webhook projection matters
  3. Proving it works

Static generation is the easy half. The half that goes wrong is invalidation: an editor publishes, the page does not change, and everyone loses faith in the CMS within a week.

Tag at three levels, not one

A single global tag rebuilds the world on every keystroke-sized edit. A tag per document leaves index pages stale forever. You want both, plus the type in between.

app/api/revalidate/route.tstypescript
const tags = ["sanity", body._type];
if (body.slug) tags.push(`${body._type}:${body.slug}`);

for (const tag of tags) {
  revalidateTag(tag, { expire: 0 });
}

Then every read declares which tags it depends on. A doc page tags doc and doc:<slug>; a collection page tags doc only, so publishing any doc refreshes the listing without touching the other doc pages.

The webhook projection matters

Sanity sends whatever projection you configure. Ask for exactly the two fields the route needs and nothing else — a fat payload is a signature-verification failure waiting to happen on a large document.

Webhook configuration
{_type, "slug": slug.current}

Proving it works

  1. Note the current age header: curl -sI https://example.com/docs/... | grep -i age.
  2. Publish a trivial edit in Studio.
  3. Poll the same URL. age should reset within a few seconds.
  4. If it does not, check the webhook delivery log in sanity.io/manage — the failure is almost always a secret mismatch.

// related

From the rest of the site.