Skip to main content

September 21, 2026

Revalidate type lists, not only references

Liveshow detail refreshed after Sanity edits but the overview stayed stale for hours. Type-based GROQ lists never matched reference-only revalidation.

Sander Korf3 min read

A fashion liveshow storefront lists every scheduled show on /liveshow and serves a detail page per show. Sanity holds titles, publish flags, and air times; Next.js caches both the overview grid and each detail route with tagged fetches. Merch edits a title in Studio. The detail URL updates within seconds after the webhook fires. Overview cards still showed yesterday's copy until the long TTL expired. Shoppers and internal QA reported the same thing: detail is right, list is wrong.

The Sanity webhook already revalidated documents that reference the edited id. GROQ like *[references($id)] fans out cache tags for pages that embed that document. The overview query does not use references. It selects every document where _type == "liveshow", sorted by air time. Publishing one liveshow never touches a singleton that points at that id, so reference-based revalidation never invalidated the overview cache entry.

Type queries and reference graphs diverge

Detail pages and embedded modules naturally reference a liveshow id. The overview is a type scan. I treated revalidate everything that shows this doc as one problem until I traced which GROQ queries fed each route. In cache terms they are two graphs. Reference revalidation reaches dependents. Listing pages depend on the set of all liveshow documents, not on inbound edges from one id.

A liveshow that flips from draft to published changes membership in that set. No existing page needs a new references() edge for the overview to be wrong. The cached list payload is stale because the set changed, not because a pointer was missing.

I read our webhook for references($id) and found coverage for detail routes and embedded modules. Nothing targeted the overview loader tag or /liveshow when _type was liveshow. TTL on the overview fetch was doing work we thought revalidation handled. Editors assumed publish meant live everywhere. The detail route proved them half right.

Bust the overview on liveshow writes

Editorial liveshow webhooks now call the same helper we use when the overview singleton publishes: invalidate overview document cache tags and run path revalidation for /liveshow plus localized listing routes. Detail revalidation stays as it was. The extra branch is cheap and runs only for that type.

if (body._type === 'liveshow') {
	await revalidateLiveshowOverview();
}
await revalidateReferences(body._id);

revalidateLiveshowOverview wraps revalidateTag for the overview fetch tags and revalidatePath for listing routes. One publish event clears both the card grid and cached fragments that list by type.

After the change, editing a show updates the overview on the next request instead of waiting for TTL. QA stopped filing duplicate tickets because the grid and the detail page finally moved together.

The rule I keep: whenever a page selects by _type or a singleton id, wire that page into the webhook explicitly. Reference fan-out alone will not reach it. I audit new listing routes the same way I audit new embeds: ask whether anything references this document, or whether the page scans a type. Sanity Studio does not know about Next cache tags. Your webhook has to bridge that gap on every query shape you ship.


Happy coding! Sander