Настройка Webhooks и интеграций Sanity
Sanity отправляет вебхуки при изменении документов в Content Lake. Это основной способ для инвалидации кэша ISR, синхронизации с поисковыми индексами и уведомлений команды.
Создание вебхука в Sanity
В sanity.io → проект → API → Webhooks → Add Webhook:
-
URL:
https://yoursite.com/api/webhooks/sanity - Trigger on: Create, Update, Delete
-
Filter:
_type == "post"(только нужные типы) - Secret: случайная строка для верификации подписи
-
Projection:
{ _id, _type, "slug": slug.current }— только нужные данные
Обработчик вебхука в Next.js
// app/api/webhooks/sanity/route.ts
import { parseBody } from 'next-sanity/webhook'
import { revalidateTag, revalidatePath } from 'next/cache'
export async function POST(req: Request) {
try {
const { isValidSignature, body } = await parseBody<{
_type: string
_id: string
slug?: string
}>(req, process.env.SANITY_WEBHOOK_SECRET!)
if (!isValidSignature) {
return Response.json({ message: 'Invalid signature' }, { status: 401 })
}
const { _type, slug } = body
// Инвалидировать по типу документа
revalidateTag(_type)
// Инвалидировать конкретную страницу
const pathMap: Record<string, string> = {
post: `/blog/${slug}`,
page: `/${slug}`,
product: `/products/${slug}`,
}
if (pathMap[_type] && slug) {
revalidatePath(pathMap[_type])
}
// Для глобальных настроек — инвалидировать всё
if (['siteSettings', 'navigation'].includes(_type)) {
revalidatePath('/', 'layout')
}
return Response.json({ revalidated: true, type: _type, slug })
} catch (err) {
return Response.json({ message: 'Webhook error' }, { status: 500 })
}
}
Синхронизация с Algolia
// scripts/sync-algolia.ts — полная индексация
import algoliasearch from 'algoliasearch'
import { createClient } from '@sanity/client'
import { toPlainText } from '@portabletext/toolkit'
const sanity = createClient({ projectId: '...', dataset: 'production', apiVersion: '2024-01-01' })
const algolia = algoliasearch(process.env.ALGOLIA_APP_ID!, process.env.ALGOLIA_ADMIN_KEY!)
const index = algolia.initIndex('posts')
const posts = await sanity.fetch(`
*[_type == "post" && defined(publishedAt)] {
"objectID": _id,
title,
"slug": slug.current,
"excerpt": excerpt,
"body": pt::text(body),
publishedAt,
"category": category->title
}
`)
await index.saveObjects(posts)
console.log(`Indexed ${posts.length} posts`)
// Обновление индекса при вебхуке
// В app/api/webhooks/sanity/route.ts — добавить:
if (_type === 'post') {
if (event === 'delete') {
await algoliaIndex.deleteObject(body._id)
} else {
const doc = await sanityClient.fetch(`
*[_id == $_id][0] {
"objectID": _id, title, "slug": slug.current, "body": pt::text(body)
}`, { _id: body._id })
if (doc) await algoliaIndex.saveObject(doc)
}
}
Уведомления в Slack
// Уведомление при публикации новой статьи
// sanity.io → Webhooks → Filter: _type == "post" && defined(publishedAt)
// app/api/webhooks/sanity-slack/route.ts
export async function POST(req: Request) {
const { isValidSignature, body } = await parseBody(req, process.env.SANITY_WEBHOOK_SECRET!)
if (!isValidSignature) return Response.json({ error: 'Unauthorized' }, { status: 401 })
await fetch(process.env.SLACK_WEBHOOK_URL!, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: `📝 Новая статья опубликована: *${body.title}*`,
attachments: [{
text: `https://yoursite.com/blog/${body.slug}`,
}],
}),
})
return Response.json({ notified: true })
}
Программный вебхук через API
import { createClient } from '@sanity/client'
const client = createClient({ projectId: '...', token: process.env.SANITY_API_TOKEN! })
// Создать вебхук через API
await client.request({
uri: '/hooks',
method: 'POST',
body: {
name: 'Next.js ISR Revalidation',
url: 'https://yoursite.com/api/webhooks/sanity',
on: [{ type: 'mutation' }],
filter: `_type in ["post", "page", "product"]`,
projection: `{ _id, _type, "slug": slug.current }`,
httpMethod: 'POST',
secret: process.env.SANITY_WEBHOOK_SECRET,
headers: {},
},
})
Сроки
Настройка вебхуков для ISR и Algolia индексации — 0,5–1 день.







