The same fashion liveshow storefront serves a grid of all shows on /liveshow and a detail page per show. Sanity holds editorial metadata; Next.js caches both the overview and the detail routes with tagged fetches. Merch edits a title or publish flag in Studio. The detail URL updates within seconds after the webhook fires. The overview cards still showed yesterday's copy until the long TTL expired. Shoppers and internal QA both reported "detail is right, list is wrong."
The Sanity webhook handler 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 lists 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 actually fed each route. In cache terms they are two graphs. Reference revalidation reaches dependents. Type 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. It is wrong because the cached list payload is stale, not because a pointer was missing.
I read our webhook for references($id) and found plenty of coverage for detail routes and embedded modules. Nothing targeted the overview loader tag or the /liveshow path when _type was liveshow. TTL on the overview fetch was doing the work we thought revalidation handled.
Bust the overview explicitly on liveshow writes
Editorial liveshow webhooks now call the same helper we use when the overview singleton itself publishes: invalidate the overview document cache tags and run path revalidation for /liveshow (and localized variants the app exposes). 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 the listing routes. One publish event clears both the card grid and any cached fragment that lists by type.
After the change, editing a show updates the overview on the next request instead of waiting for TTL. The trap I remember: 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.
Happy coding! Sander