אינטגרציית CMS ראשית מלאה: Sanity עם Next.js, Nuxt ו-Remix
אינטגרציית Sanity המפתח-ביד שלנו כוללת הגדרת Sanity Client, webhooks ועוד. כאשר בונים עם Next.js, הידרציה של תוכן מ-Sanity הופכת לעיתים קרובות לצוואר בקבוק, ויש צורך בהטמעת מצב תצוגה מקדימה עבור עורכים. ללא ISR, כל פרסום תוכן גורם לבנייה מחדש מלאה, ומעלה את TTFB ב-30–50%. אנו מקימים אינטגרציה מלאה: ISR, webhooks וטיפוסים. רקורד שלנו: 10+ פרויקטי Sanity, 5 שנים בשוק. אנו מבטיחים 100% שביעות רצון עם תהליך האינטגרציה המוכח שלנו. צרו קשר לקבלת ייעוץ והערכה לפרויקט שלכם. עלות ממוצעת לחבילת Basic היא $1,500. לקוחות בדרך כלל חוסכים 20-30% בעלויות שרת עם ISR.
Sanity עובד על HTTP API — כל framework יכול לקרוא את התוכן שלו. ספריות רשמיות: next-sanity עבור Next.js, @sanity/client עבור אחרים. עם ה-CDN, Sanity Client מגיב ב-50–80 אלפיות השנייה בממוצע, מהיר פי 2–3 מבקשות fetch פשוטות ל-REST API טיפוסי. תיעוד Sanity הרשמי
יתרונות Sanity עם Server Frameworks
Sanity הוא CMS ראשי בזמן אמת: עורכים רואים שינויים מיידית. בשילוב עם Next.js או Nuxt, מקבלים דפים סטטיים שמתעדכנים רק כשהתוכן משתנה. שימוש ב-ISR מפחית עומס שרת בעד 40% ומשפר TTFB ב-25%. בנוסף, שאילתות GROQ מאפשרות לבחור רק את השדות הדרושים — נפח הנתונים המועבר קטן ב-60–70%.
חיבור Sanity ל-Next.js דרך App Router
התקינו חבילות: npm install next-sanity @sanity/client. הגדירו את ה-client. עבור טיוטות, השתמשו ב-client תצוגה מקדימה עם token.
// 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: process.env.SANITY_API_VERSION, // установите актуальную версию
useCdn: process.env.NODE_ENV === 'production',
})
export const previewClient = createClient({
projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!,
dataset: process.env.NEXT_PUBLIC_SANITY_DATASET || 'production',
apiVersion: process.env.SANITY_API_VERSION,
useCdn: false,
token: process.env.SANITY_API_TOKEN,
perspective: 'previewDrafts',
})ב-server components, השתמשו ב-// 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: process.env.SANITY_API_VERSION, // установите актуальную версию useCdn: process.env.NODE_ENV === 'production', }) export const previewClient = createClient({ projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!, dataset: process.env.NEXT_PUBLIC_SANITY_DATASET || 'production', apiVersion: process.env.SANITY_API_VERSION, useCdn: false, token: process.env.SANITY_API_TOKEN, perspective: 'previewDrafts', }) עם שאילתות GROQ. דוגמה לדף פוסט:
// app/posts/[slug]/page.tsx
import { client, previewClient } 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 }))
} Revalidation לפי דרישה דרך ISR ו-Webhooks
ISR מעדכן דפים רק כשהתוכן משתנה — מפחית עומס שרת. צרו webhook ב-Sanity ו-endpoint שקורא ל-client.fetch. דוגמה:
// 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
התקינו את מודול // app/posts/[slug]/page.tsx import { client, previewClient } 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 })) } והוסיפו הגדרה ב-revalidateTag.
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxtjs/sanity'],
sanity: {
projectId: process.env.NUXT_PUBLIC_SANITY_PROJECT_ID,
dataset: 'production',
apiVersion: process.env.SANITY_API_VERSION,
useCdn: process.env.NODE_ENV === 'production',
},
})ברכיב, השתמשו ב-// 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 }) } } עבור שאילתות fetch:
<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> אינטגרציה של Sanity עם Remix
ב-Remix, נתונים נטענים בפונקציות @nuxtjs/sanity בשרת — הדף מרנדר עם תוכן כבר, ללא עיכובי הידרציה או בקשות waterfall מצד הלקוח. השתמשו ב-nuxt.config.ts ישירות ב-loader: // nuxt.config.ts export default defineNuxtConfig({ modules: ['@nuxtjs/sanity'], sanity: { projectId: process.env.NUXT_PUBLIC_SANITY_PROJECT_ID, dataset: 'production', apiVersion: process.env.SANITY_API_VERSION, useCdn: process.env.NODE_ENV === 'production', }, }) , ואז העבירו נתונים לרכיב דרך $sanity.
Remix אינו תומך ב-ISR מחוץ לקופסה. עבור caching, השתמשו ב-HTTP headers כמו <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> ב-loader — Cloudflare ו-Vercel Edge קולטים אותם אוטומטית. Webhook של Sanity מבטל את ה-cache דרך Cloudflare Purge API כשמפרסמים תוכן חדש. זמן תגובה ממוצע עם CDN מוגדר כראוי הוא 60–100 אלפיות השנייה.
טיפוסי תוכן עם Sanity TypeGen
הפקודה loader יוצרת טיפוסי TypeScript מסכמת הסטודיו. שאילתות GROQ הופכות ל-type-safe: ה-IDE מציע שדות, TypeScript תופס שגיאות כתיב בזמן בנייה. זה חוסך 1–2 שעות לכל סוג תוכן חדש ומפחית באגים בייצור.
השוואת Frameworks: מהירות עם Sanity Client
| Framework | זמן תגובה ראשון (TTFB) | קלות הגדרה | תיעוד |
|---|---|---|---|
| Next.js | נמוך (ISR/SSR) | גבוהה | מצוין |
| Nuxt 3 | בינוני (SSR/SSG) | גבוהה | טוב |
| Remix | נמוך (loader) | בינוני | טוב |
בפועל, Sanity Client מהיר פי 2–3 מבקשות fetch פשוטות בזכות CDN ו-caching יעיל.
טעויות אינטגרציה נפוצות
-
חוסר התאמה בהידרציה עקב הבדל נתונים בין שרת ללקוח — נפתר על ידי סנכרון שאילתות דרך
@sanity/client. - שגיאות CORS בעת ביצוע בקשות מהדפדפן — הגדירו CORS origins בלוח הבקרה של Sanity.
-
שאילתות N+1 עם הפניות מקוננות — השתמשו באופרטור GROQ
client.fetch(query, { slug: params.slug })כדי להביא מסמכים קשורים. - Cache מיושן עם ISR — ודאו ש-webhook secret נכון וה-endpoint נגיש.
מה כלול בעבודה
- הגדרת Sanity Client ותצורת גרסת API
- הטמעת מצב תצוגה מקדימה (draft mode) עם tokens
- Webhook עבור revalidation לפי דרישה
- שאילתות GROQ וטיפוסי תוכן (טיפוסים שנוצרו)
- אופטימיזציית תמונות דרך Sanity Image CDN
- תיעוד והדרכת צוות
איך התהליך עובד
- ניתוח: דיון בארכיטקטורת תוכן וצרכי שאילתות
- עיצוב: סכמות שאילתות GROQ, טיפוסים
- הטמעה: אינטגרציית client, דפים, תצוגה מקדימה
- בדיקות: אימות הידרציה, שגיאות revalidation
- פריסה: הגדרת משתני סביבה, webhook
לוחות זמנים ומחירים
| חבילה | מה כלול | לוח זמנים |
|---|---|---|
| Basic | Next.js + ISR + Webhooks | 1–2 ימים |
| Extended | +Nuxt או Remix, מצב תצוגה מקדימה | 2–3 ימים |
| Turnkey | כל ה-frameworks + TypeGen + תיעוד | 3–5 ימים |
העלות נקבעת לאחר ניתוח הפרויקט הספציפי שלכם. זמן משוער: Next.js App Router — 1–2 ימים, Nuxt 3 — יום אחד, Remix — יום אחד, כל שלושת ה-frameworks — 3–5 ימים. עלות ממוצעת לאינטגרציה של framework יחיד היא $1,500–$3,000. הזמינו את האינטגרציה — נכין הערכה מדויקת. קבלו ייעוץ: המהנדסים שלנו יעזרו לכם לבחור את הארכיטקטורה האופטימלית.







