
Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag storeYou're absolutely right that webhooks in Sanity don't support subqueries in their GROQ projections - you can't use *[...] inside webhook payloads. This is a documented limitation that affects exactly the use case you're describing.
The good news is there's a solid workaround: have your Next.js API route do the GROQ query when it receives the webhook. This is the standard production-ready approach that many Sanity users rely on.
Here's how to handle this:
1. Set up a simple webhook that sends just the basic document info (no subqueries needed):
In your Sanity project settings, create a webhook with a projection like:
{
"_id": _id,
"_type": _type,
"slug": slug.current
}2. Create a Next.js API route that receives the webhook and does the heavy lifting:
// app/api/revalidate-webhook/route.js (App Router)
import { createClient } from '@sanity/client';
import { revalidatePath } from 'next/cache';
const client = createClient({
projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,
dataset: process.env.NEXT_PUBLIC_SANITY_DATASET,
apiVersion: '2024-01-01',
useCdn: false,
token: process.env.SANITY_API_TOKEN, // Read token
});
export async function POST(request) {
const { _id, _type, slug } = await request.json();
// For your theme example:
if (_type === 'theme') {
// Revalidate the theme page itself
if (slug) {
revalidatePath(`/themes/${slug}`);
}
// NOW you can do the subquery to find related blogs
const relatedBlogs = await client.fetch(
`*[_type == "blog" && references($themeId)]{ "slug": slug.current }`,
{ themeId: _id }
);
// Revalidate each related blog post
for (const blog of relatedBlogs) {
revalidatePath(`/blog/${blog.slug}`);
}
console.log(`Revalidated ${relatedBlogs.length} blog posts referencing theme ${_id}`);
}
return Response.json({
revalidated: true,
count: relatedBlogs?.length || 0
});
}For Pages Router, use this instead:
// pages/api/revalidate-webhook.js
import { createClient } from '@sanity/client';
const client = createClient({
projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,
dataset: process.env.NEXT_PUBLIC_SANITY_DATASET,
apiVersion: '2024-01-01',
useCdn: false,
token: process.env.SANITY_API_TOKEN,
});
export default async function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).json({ message: 'Method not allowed' });
}
const { _id, _type, slug } = req.body;
if (_type === 'theme') {
if (slug) {
await res.revalidate(`/themes/${slug}`);
}
const relatedBlogs = await client.fetch(
`*[_type == "blog" && references($themeId)]{ "slug": slug.current }`,
{ themeId: _id }
);
for (const blog of relatedBlogs) {
await res.revalidate(`/blog/${blog.slug}`);
}
}
return res.json({ revalidated: true });
}This approach solves your problem because:
references() functionUse revalidateTag for more flexibility: If you're using Next.js 13+ with cache tags, you can tag related content and revalidate by tag instead of individual paths:
// Tag your fetches when building pages
fetch(sanityQuery, { next: { tags: [`theme-${themeId}`] } })
// Then revalidate all content with that tag at once
revalidateTag(`theme-${themeId}`);Secure your webhook endpoint: Add validation to ensure requests are actually from Sanity using webhook signatures or a secret token.
Handle the references() function: The GROQ references() function is perfect for finding documents that reference your changed document - exactly what you need for the theme → blog relationship. It works in your API route queries even though it won't work in webhook projections.
This webhook + API route pattern is the established, production-ready solution that works reliably for Next.js on-demand revalidation scenarios with Sanity.
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