
Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag storeYour 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:
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.
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.
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 parallelIf 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.
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 })
}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 deployYour 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.
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 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.
Content operations
Content backend


The only platform powering content operations
By Industry


Tecovas strengthens their customer connections
Build and Share

Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag store