Strategies for revalidating pages after site-wide changes in Sanity discussed in Slack thread
Your approach with webhooks is solid, but the timeout issue is common with sequential revalidation. Here are several strategies to handle site-wide revalidation more efficiently:
1. Use revalidateTag Instead of Individual Paths (Best Solution)
The most efficient approach is using tag-based revalidation with revalidateTag. Tag all pages that depend on your navigation data, then revalidate the tag once:
// In your data fetching (each page)
const navigation = await sanityFetch({
query: NAVIGATION_QUERY,
tags: ['navigation'], // Tag this data
})
// In your API route (app/api/revalidate/route.ts)
import { revalidateTag } from 'next/cache'
export async function POST(request: Request) {
// Validate webhook
revalidateTag('navigation') // Single call invalidates all 45 pages
return Response.json({ revalidated: true })
}This is far more efficient than looping through 45 URLs and won't cause timeouts since you're only making one revalidation call that invalidates all pages using that tag. As the documentation explains, tag-based revalidation is ideal for connected content where multiple paths depend on the same data.
2. Return Early from Your Webhook Handler
Your current code awaits all promises before returning a response, which causes the timeout. Instead, return the response immediately and let revalidation happen in the background:
// pages/api/revalidate.ts
export default async function handler(req, res) {
// Return success immediately
res.status(200).json({ revalidated: true })
// Revalidate in background (no await before the return)
const revalidatePromises = revalidateUrls.map(url =>
res.revalidate(url).catch(err => console.error(`Failed to revalidate ${url}`, err))
)
// This happens after response is sent
Promise.all(revalidatePromises)
}The key is responding to the webhook quickly so Sanity/Vercel doesn't timeout, while the actual revalidation work continues in the background.
3. Remove Redundant await in Your Loop
Your current code has both await promise inside the loop and Promise.all(revalPromises) at the end, which makes things sequential and slow:
// Current (slow):
for (const url of revalidateUrls) {
const promise = res.revalidate(urlPath);
revalPromises.push(promise);
await promise; // This makes it sequential!
}
await Promise.all(revalPromises); // Redundant
// Better (parallel):
const revalPromises = revalidateUrls.map(url =>
res.revalidate(url)
)
await Promise.all(revalPromises) // All run in parallel4. Use Vercel Background Functions
If you're on Vercel and need to await all revalidations, you can use background functions by setting isBackground: true or adding the x-vercel-background header. This allows your function to run for up to 5 minutes:
export const config = {
maxDuration: 300, // 5 minutes for Pro/Enterprise plans
}
export default async function handler(req, res) {
res.setHeader('x-vercel-background', '1')
// Now you can await all revalidations
await Promise.all(revalidateUrls.map(url => res.revalidate(url)))
return res.status(200).json({ revalidated: true })
}Note: Background functions require a Vercel Pro or Enterprise plan.
5. Use Next.js unstable_after (App Router)
If you're using the App Router (Next.js 15+), you can use unstable_after to defer work until after the response is sent:
import { unstable_after as after } from 'next/server'
import { revalidatePath } from 'next/cache'
export async function POST(request: Request) {
after(async () => {
// This runs after the response is sent
await Promise.all(
revalidateUrls.map(path => revalidatePath(path))
)
})
return Response.json({ revalidated: true })
}6. Consider Sanity Functions (Modern Alternative to Webhooks)
Instead of webhooks, you could use Sanity Functions, which run within Sanity's infrastructure and can react to content changes. They're the modern, recommended approach for automation with better security, no external hosting needed, and 900-second timeouts.
First, initialize blueprints in your Sanity project:
npx sanity blueprints init
npx sanity blueprints add function --name revalidate-on-nav-changeThen configure your function in sanity.blueprint.ts:
import {defineBlueprint, defineDocumentFunction} from '@sanity/blueprints'
export default defineBlueprint({
resources: [
defineDocumentFunction({
type: 'sanity.function.document',
src: './functions/revalidate-on-nav-change',
name: 'revalidate-on-nav-change',
timeout: 60,
event: {
on: ['create', 'update'],
filter: '_type == "navigation"',
projection: '{_id}',
},
}),
],
})In your function handler (functions/revalidate-on-nav-change/index.ts):
export default async function handler() {
await fetch('https://yoursite.com/api/revalidate', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.REVALIDATE_SECRET}`
}
})
}Deploy with:
npx sanity blueprints deploy7. Full Rebuild as Last Resort
Your idea of triggering a full rebuild via Vercel deploy hook is valid for infrequent changes, but it's overkill for 45 pages and navigation updates. Reserve this for major structural changes.
My Recommendation
Start with revalidateTag if you're on App Router - it's the cleanest, most efficient solution that won't timeout. If you're on Pages Router, use the return early pattern (#2) combined with parallel execution to avoid timeouts. The key insight is that you don't need to wait for revalidation to complete before responding to the webhook.
Sanity – Build the way you think, not the way your CMS thinks
Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.