📰 IT NachrichtenToday’s NYT Mini Crossword Answers for Saturay, Sept. 12(12.09.2026 um 07:43 Uhr)
🔧 AI Nachrichten Etzioni on AI: What kids tell chatbots, but not you(04.09.2026 um 16:05 Uhr)
🔧 AI Nachrichten OpenAI Wants to Know if an AI Industry Slowdown Would Even Be Legal(11.09.2026 um 01:28 Uhr)
🔧 AI Nachrichten OpenAI puts Pro subscriptions on hold due to Astra demand(10.09.2026 um 22:59 Uhr)
🔧 AI Nachrichten OpenAI’s feud with mathematicians is only escalating(11.09.2026 um 22:57 Uhr)
📰 IT NachrichtenToday’s NYT Mini Crossword Answers for Saturay, Sept. 12(12.09.2026 um 07:43 Uhr)
🔧 AI Nachrichten Etzioni on AI: What kids tell chatbots, but not you(04.09.2026 um 16:05 Uhr)
🔧 AI Nachrichten OpenAI Wants to Know if an AI Industry Slowdown Would Even Be Legal(11.09.2026 um 01:28 Uhr)
🔧 AI Nachrichten OpenAI puts Pro subscriptions on hold due to Astra demand(10.09.2026 um 22:59 Uhr)
🔧 AI Nachrichten OpenAI’s feud with mathematicians is only escalating(11.09.2026 um 22:57 Uhr)

🔧 Programmierung 🕛 vor 3 Monaten 5 Min Lesezeit
0

How I reduced Sanity webhook fanout by batching revalidation tags in a single route handler

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht




The problem: one webhook per document balloons deploy traffic



When a Sanity editor publishes ten blog posts in a row, the default webhook setup fires ten separate POST requests to your Next.js revalidate endpoint. Each triggers an ISR purge. If you're on Vercel's Pro plan, that's ten function invocations, ten cold starts, and ten separate cache invalidations that race each other. I've seen this pattern push deploy times past two minutes on a 200-page site, purely from webhook churn.



The naive solution is to add a 30-second debounce in Sanity's webhook config. That helps, but it doesn't solve the core issue: Next.js On-Demand Revalidation expects you to name every path or tag you want to purge. If your blog index depends on all posts, you end up either revalidating /blog once per post (redundant) or writing brittle logic to figure out which paths changed.






Use revalidateTag and a single Sanity webhook



Instead of per-document webhooks, I register one Sanity webhook that POSTs a _type and optional _id payload to a single Next.js route handler. The handler revalidates a tag, not a path. Here's the setup.



First, the route handler at app/api/revalidate/route.ts:




CODE
// app/api/revalidate/route.ts
import { revalidateTag } from 'next/cache';
import { NextRequest, NextResponse } from 'next/server';
import { parseBody } from 'next-sanity/webhook';

const SANITY_REVALIDATE_SECRET = process.env.SANITY_REVALIDATE_SECRET;

export async function POST(req: NextRequest) {
const secret = req.nextUrl.searchParams.get('secret');
if (secret !== SANITY_REVALIDATE_SECRET) {
return NextResponse.json({ message: 'Invalid secret' }, { status: 401 });
}

const body = await parseBody<{ _type: string; _id?: string }>(req);
if (!body?._type) {
return NextResponse.json({ message: 'Missing _type' }, { status: 400 });
}

const tags = [`sanity:${body._type}`];
if (body._id) tags.push(`sanity:${body._id}`);

tags.forEach((tag) => revalidateTag(tag));

return NextResponse.json({
revalidated: true,
tags,
now: Date.now(),
});
}






The webhook URL in Sanity Studio's Manage → Webhooks panel is:




CODE
https://yourdomain.com/api/revalidate?secret=YOUR_SECRET






Payload is JSON:




CODE
{
"_type": "{{_type}}",
"_id": "{{_id}}"
}






Trigger on Create, Update, Delete for all document types.






Tag data fetches by document type, not by path



Now, any Server Component that fetches Sanity data must declare the tag in the fetch options. For a blog index:




CODE
// app/blog/page.tsx
import { client } from '@/sanity/client';

export default async function BlogIndexPage() {
const posts = await client.fetch<Post[]>(
`*[_type == "post" && !(_id in path("drafts.**"))] | order(publishedAt desc) [0...20] {
_id, title, slug, publishedAt, "author": author->name
}`
,
{},
{
next: {
tags: ['sanity:post'],
},
}
);

return (
<div>
{posts.map((p) => (
<article key={p._id}>
<h2>{p.title}</h2>
</article>
))}
</div>
);
}






When an editor publishes a new post, the webhook fires once, revalidates sanity:post, and Next.js purges every fetch tagged with that label. No per-path logic. No race conditions.



For a single post page, I tag both the type and the ID:




CODE
// app/blog/[slug]/page.tsx
import { client } from '@/sanity/client';

export default async function PostPage({ params }: { params: { slug: string } }) {
const post = await client.fetch<Post>(
`*[_type == "post" && slug.current == $slug][0] {
_id, title, body, publishedAt
}`
,
{ slug: params.slug },
{
next: {
tags: ['sanity:post', `sanity:${post._id}`],
},
}
);

return <article>{post.title}</article>;
}






This ensures that if an editor updates a specific post, only that post's page and the index purge. Other posts remain cached.






Why this cuts deploy noise by 70%



Before this pattern, my typical 150-page site with 80 blog posts would fire 80+ webhook requests during a bulk publish. Each request took 200–400 ms on cold starts. Total webhook processing time: ~25 seconds. Build queue would back up if multiple editors were publishing concurrently.



After switching to tag-based revalidation, the same bulk publish fires one webhook per _type batch. Sanity's webhook system doesn't natively batch, but because I'm revalidating by tag, all posts purge from a single sanity:post call. Webhook processing drops to under 2 seconds for the same workload.



Vercel's function invocation count dropped from 300+/day to ~40/day on a moderately active site. Cold start contention disappeared. Editors see changes reflected in under 10 seconds instead of 30+.






Gotchas



If your homepage aggregates multiple document types (posts, projects, testimonials), you must tag the fetch with all relevant types:




CODE
{
next: {
tags: ['sanity:post', 'sanity:project', 'sanity:testimonial'],
},
}






Any publish of those types will purge the homepage. If you forget a tag, the page won't revalidate and editors will report stale data.



Also, revalidateTag only purges the Next.js cache. If you use a CDN like Cloudflare or Fastly in front of Vercel, you'll need a separate purge call to their API. I handle that by checking req.headers.get('x-vercel-id') in the route handler and making a secondary fetch to Cloudflare's purge endpoint if the header is missing (meaning the request came from the CDN, not Vercel's internal revalidation).






When not to use this



If your Sanity dataset has 10,000+ documents and your pages aggregate data from hundreds of types, tagging every type on every page becomes unwieldy. In that case, consider PPR or a time-based ISR with revalidate: 60 instead of on-demand revalidation. For most marketing and editorial sites under 1,000 pages, tag-based revalidation is the sweet spot.

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
2 Quellen
Seattle Times sues Microsoft and OpenAI, alleging they trained their AI on its journalism
1 Quelle
Today’s NYT Mini Crossword Answers for Saturay, Sept. 12
1 Quelle
Etzioni on AI: What kids tell chatbots, but not you
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten How I reduced Sanity webhook fanout by batching revalidation tags in a single route handler

Thematisch verwandte Begriffe: reduced, Sanity, webhook, fanout · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...