Интеграция Sanity с Next.js / Nuxt.js / Remix
Sanity работает через HTTP API — любой фреймворк может читать контент. Официальные библиотеки: next-sanity для Next.js, @sanity/client для остальных.
Next.js App Router
npm install next-sanity @sanity/client @portabletext/react @sanity/image-url
// lib/sanity/client.ts
import { createClient } from 'next-sanity'
export const client = createClient({
projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!,
dataset: process.env.NEXT_PUBLIC_SANITY_DATASET || 'production',
apiVersion: '2024-01-01',
useCdn: process.env.NODE_ENV === 'production',
})
// Для черновиков (preview mode)
export const previewClient = createClient({
projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!,
dataset: process.env.NEXT_PUBLIC_SANITY_DATASET || 'production',
apiVersion: '2024-01-01',
useCdn: false,
token: process.env.SANITY_API_TOKEN,
perspective: 'previewDrafts',
})
// app/posts/[slug]/page.tsx
import { client } from '@/lib/sanity/client'
import { groq } from 'next-sanity'
import { draftMode } from 'next/headers'
const query = groq`*[_type == "post" && slug.current == $slug][0] {
_id, title, body, publishedAt,
"author": author->{ name },
"slug": slug.current
}`
export default async function PostPage({ params }: { params: { slug: string } }) {
const { isEnabled } = draftMode()
const activeClient = isEnabled ? previewClient : client
const post = await activeClient.fetch(query, { slug: params.slug }, {
next: { tags: [`post-${params.slug}`] },
})
if (!post) notFound()
return <Article post={post} />
}
export async function generateStaticParams() {
const slugs = await client.fetch<string[]>(`*[_type == "post"].slug.current`)
return slugs.filter(Boolean).map(slug => ({ slug }))
}
On-demand Revalidation
// app/api/revalidate/route.ts
import { revalidateTag } from 'next/cache'
import { parseBody } from 'next-sanity/webhook'
export async function POST(req: Request) {
try {
const { isValidSignature, body } = await parseBody<{ _type: string; slug?: { current: string } }>(
req,
process.env.SANITY_WEBHOOK_SECRET!
)
if (!isValidSignature) {
return Response.json({ message: 'Invalid signature' }, { status: 401 })
}
if (body._type === 'post') {
revalidateTag('posts')
if (body.slug?.current) {
revalidateTag(`post-${body.slug.current}`)
}
}
return Response.json({ revalidated: true })
} catch (err) {
return Response.json({ message: 'Invalid request' }, { status: 400 })
}
}
Nuxt 3 (@nuxt/sanity)
npm install @nuxtjs/sanity
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxtjs/sanity'],
sanity: {
projectId: process.env.NUXT_PUBLIC_SANITY_PROJECT_ID,
dataset: 'production',
apiVersion: '2024-01-01',
useCdn: process.env.NODE_ENV === 'production',
},
})
<!-- pages/posts/[slug].vue -->
<template>
<article v-if="post">
<h1>{{ post.title }}</h1>
<SanityContent :blocks="post.body" />
</article>
</template>
<script setup lang="ts">
const { $sanity } = useNuxtApp()
const route = useRoute()
const { data: post } = await useAsyncData(`post-${route.params.slug}`, () =>
$sanity.fetch(
`*[_type == "post" && slug.current == $slug][0] { title, body }`,
{ slug: route.params.slug }
)
)
</script>
Remix
// app/routes/posts.$slug.tsx
import { json, type LoaderFunctionArgs } from '@remix-run/node'
import { useLoaderData } from '@remix-run/react'
import { createClient } from '@sanity/client'
const client = createClient({
projectId: process.env.SANITY_PROJECT_ID!,
dataset: 'production',
apiVersion: '2024-01-01',
useCdn: true,
})
export async function loader({ params }: LoaderFunctionArgs) {
const post = await client.fetch(
`*[_type == "post" && slug.current == $slug][0] { title, body, publishedAt }`,
{ slug: params.slug }
)
if (!post) throw new Response('Not Found', { status: 404 })
return json(post)
}
export default function PostPage() {
const post = useLoaderData<typeof loader>()
return (
<article>
<h1>{post.title}</h1>
</article>
)
}
Санировать изображения через Sanity Image CDN
import imageUrlBuilder from '@sanity/image-url'
import { client } from '@/lib/sanity/client'
const builder = imageUrlBuilder(client)
export function sanityImageUrl(source: any) {
return builder.image(source)
}
// Использование
const url = sanityImageUrl(post.mainImage)
.width(800)
.height(450)
.crop('entropy') // умная обрезка
.auto('format') // WebP если поддерживается
.quality(80)
.url()
Сроки
Интеграция Sanity с Next.js App Router (ISR, webhooks, preview mode) — 1–2 дня. Для Nuxt или Remix — 1 день.







