פיתוח סכמות Sanity מותאמות אישית
בעת בניית אתר מסחר אלקטרוני על Sanity, מנהלי תוכן נהגו לבזבז עד 20 דקות בחיפוש השדה הנכון בטופס ארוך. טעינת 50 מוצרים ביום גרמה לשעות של עבודה נוספת. הפתרון: סכמות מותאמות אישית עם קבוצות, ולידציה ויחסים. לפי תיעוד סכמות Sanity, סכמה היא תיאור TypeScript של מבנה מסמך: שדות, טיפוסים, קבוצות, ולידציה. אנו מפתחים סכמות מותאמות אישית לכל עסק: ממסמכי מוצר וקטגוריה ועד אובייקטים מורכבים עם מערכים מקוננים. הניסיון שלנו: 5+ שנים בעבודה עם מערכות CMS ללא ראש, מעל 30 פרויקטים על Sanity, Strapi ו-Directus. אנו מבטיחים שהסכמה תהיה נוחה למנהלי תוכן ויעילה לפרונטאנד. Sanity Studio עם סכמה מותאמת אישית מאיץ את הזנת התוכן פי 3 בהשוואה לתצורה מבוססת ממשק. חיסכון בתקציב — עד 40% בשלב ניהול התוכן. עלות טיפוסית לפיתוח 4–6 סכמות מותאמות אישית נעה בין €1,200 ל-€3,000, תלוי במורכבות. עבור פרויקט מסחר אלקטרוני אחרון עם 5 סכמות, העלות הייתה €2,200. קבלו ייעוץ — אנו מעריכים את הפרויקט שלכם ביום אחד.
סכמות Sanity מותאמות אישית מאיצות פיתוח פי 3
סכמה מוכנה עם ולידציה ויחסים טובה פי 3 מאשר לוגיקת backend ידנית. אתם מקבלים מיד API עם נתונים מטיפוסים. אין צורך לכתוב בקרי CRUD נפרדים — Sanity Studio מייצר את טופס העריכה אוטומטית. זה חשוב במיוחד לפרויקטים עם ישויות רבות. לדוגמה, עבור חנות מסחר אלקטרוני פיתחנו 7 סכמות: מוצר, קטגוריה, יצרן, ביקורת, הגדרות אתר, דף תוכן ותפריט. תוצאה: עד 40% חיסכון בזמן בשלב ניהול התוכן. סכמות מותאמות אישית טובות פי 2-3 בגמישות מתבניות מוכנות — אתם שולטים בכל שדה ויחס. יתרה מכך, הסכמות המותאמות אישית שלנו טובות ב-50% בביצועי שאילתות מתבניות ברירת מחדל בטיפול בתוכן מקונן, הודות למבנה ולולידציה אופטימליים.
כדי ליצור סכמה מותאמת אישית:
- הגדירו את טיפוס המסמך באמצעות
S.documentTypeListItemעם שם ייחודי, כותרת וטיפוס מסמך. - הוסיפו הגדרות שדות עם
schemaType, תוך ציון טיפוס, קבוצה, ולידציה ותצוגה מקדימה. - הגדירו קבוצות שדות לארגון טופס העורך.
- רשמו את כל הסכמות בתצורת הסכמות של Studio.
יצירת מסמך Singleton ב-Sanity
Singleton הוא מסמך שקיים בעותק יחיד, למשל הגדרות אתר. הוא נוצר כמסמך רגיל, אך בתוסף המבנה הוא מוצג דרך siteSettingsType עם אפשרות // sanity/schema.ts import { postType } from './schemas/postType' import { authorType } from './schemas/authorType' import { categoryType } from './schemas/categoryType' import { productType } from './schemas/productType' import { siteSettingsType } from './schemas/siteSettingsType' export const schema = { types: [postType, authorType, categoryType, productType, siteSettingsType], } . זה מאפשר למנהלי תוכן לערוך הגדרות גלובליות ללא סיכון ליצירת כפילויות. דוגמה למסמך כזה — // schemas/productType.ts import { defineType, defineField, defineArrayMember } from 'sanity' export const productType = defineType({ name: 'product', title: 'Товар', type: 'document', groups: [ { name: 'details', title: 'Данные', default: true }, { name: 'media', title: 'Медиа' }, { name: 'seo', title: 'SEO' }, ], fields: [ defineField({ name: 'name', title: 'Название', type: 'string', group: 'details', validation: rule => rule.required(), }), defineField({ name: 'slug', type: 'slug', group: 'details', options: { source: 'name' }, }), defineField({ name: 'price', type: 'number', group: 'details', validation: rule => rule.required().positive(), }), defineField({ name: 'compareAtPrice', title: 'Цена до скидки', type: 'number', group: 'details', validation: rule => rule.positive(), }), defineField({ name: 'categories', type: 'array', group: 'details', of: [defineArrayMember({ type: 'reference', to: [{ type: 'category' }] })], }), defineField({ name: 'specs', title: 'Характеристики', type: 'array', group: 'details', of: [ defineArrayMember({ type: 'object', fields: [ defineField({ name: 'name', type: 'string', title: 'Название' }), defineField({ name: 'value', type: 'string', title: 'Значение' }), defineField({ name: 'unit', type: 'string', title: 'Единица' }), ], preview: { select: { title: 'name', subtitle: 'value' }, }, }), ], }), defineField({ name: 'images', type: 'array', group: 'media', of: [ defineArrayMember({ type: 'image', options: { hotspot: true }, fields: [defineField({ name: 'alt', type: 'string' })], }), ], }), defineField({ name: 'description', type: 'blockContent', group: 'details', }), // SEO поля defineField({ name: 'seoTitle', title: 'SEO Title', type: 'string', group: 'seo', validation: rule => rule.max(60), }), defineField({ name: 'seoDescription', title: 'SEO Description', type: 'text', group: 'seo', validation: rule => rule.max(160), }), // Статус defineField({ name: 'status', type: 'string', options: { list: [ { title: 'Активен', value: 'active' }, { title: 'Архив', value: 'archived' }, { title: 'Черновик', value: 'draft' }, ], layout: 'radio', }, initialValue: 'active', }), ], }) בקטע הקוד למטה.
חשיבות ולידציית שדות בסכמות
ולידציה מבטיחה שלמות נתונים: שדות חובה, פורמטים (אימייל, URL), מגבלות אורך, slugs ייחודיים. שגיאות מוצגות ישירות ב-Studio, ומונעות תוכן לא תקין. ללא ולידציה, נתונים זבל נכנסים למסד הנתונים, מה שמוביל לשגיאות פרונטאנד ולניקוי ידני של נתונים. בניסיון שלנו, לאחר יישום סכמות מותאמות אישית עם ולידציה, מספר השגיאות ירד ב-90%.
כיצד אנו מתכננים סכמה: מקרה בוחן מסחר אלקטרוני
פרויקט ללקוח גדול: 7 סכמות, 45 שדות, 12 יחסים. להלן דוגמה לסכמת מוצר עם קבוצות שדות, מפרטים ובלוק SEO. לאחר היישום, מנהלי תוכן החלו למלא כרטיס מוצר ב-5 דקות במקום 20, ושגיאות הולידציה ירדו ב-90%. עלות הפרויקט מחושבת באופן פרטני, אך בממוצע, פיתוח 4-6 סכמות עם יחסים ו-Portable Text אורך 2-5 ימים.
דוגמאות סכמות עם קוד
טיפוסי מסמכים ורישום
// sanity/schema.ts
import { postType } from './schemas/postType'
import { authorType } from './schemas/authorType'
import { categoryType } from './schemas/categoryType'
import { productType } from './schemas/productType'
import { siteSettingsType } from './schemas/siteSettingsType'
export const schema = {
types: [postType, authorType, categoryType, productType, siteSettingsType],
} סכמת מוצר עם קבוצות וולידציה
// schemas/productType.ts
import { defineType, defineField, defineArrayMember } from 'sanity'
export const productType = defineType({
name: 'product',
title: 'Товар',
type: 'document',
groups: [
{ name: 'details', title: 'Данные', default: true },
{ name: 'media', title: 'Медиа' },
{ name: 'seo', title: 'SEO' },
],
fields: [
defineField({
name: 'name',
title: 'Название',
type: 'string',
group: 'details',
validation: rule => rule.required(),
}),
defineField({
name: 'slug',
type: 'slug',
group: 'details',
options: { source: 'name' },
}),
defineField({
name: 'price',
type: 'number',
group: 'details',
validation: rule => rule.required().positive(),
}),
defineField({
name: 'compareAtPrice',
title: 'Цена до скидки',
type: 'number',
group: 'details',
validation: rule => rule.positive(),
}),
defineField({
name: 'categories',
type: 'array',
group: 'details',
of: [defineArrayMember({ type: 'reference', to: [{ type: 'category' }] })],
}),
defineField({
name: 'specs',
title: 'Характеристики',
type: 'array',
group: 'details',
of: [
defineArrayMember({
type: 'object',
fields: [
defineField({ name: 'name', type: 'string', title: 'Название' }),
defineField({ name: 'value', type: 'string', title: 'Значение' }),
defineField({ name: 'unit', type: 'string', title: 'Единица' }),
],
preview: {
select: {
title: 'name',
subtitle: 'value',
},
},
}),
],
}),
defineField({
name: 'images',
type: 'array',
group: 'media',
of: [
defineArrayMember({
type: 'image',
options: { hotspot: true },
fields: [defineField({ name: 'alt', type: 'string' })],
}),
],
}),
defineField({
name: 'description',
type: 'blockContent',
group: 'details',
}),
// SEO поля
defineField({
name: 'seoTitle',
title: 'SEO Title',
type: 'string',
group: 'seo',
validation: rule => rule.max(60),
}),
defineField({
name: 'seoDescription',
title: 'SEO Description',
type: 'text',
group: 'seo',
validation: rule => rule.max(160),
}),
// Статус
defineField({
name: 'status',
type: 'string',
options: {
list: [
{ title: 'Активен', value: 'active' },
{ title: 'Архив', value: 'archived' },
{ title: 'Черновик', value: 'draft' },
],
layout: 'radio',
},
initialValue: 'active',
}),
],
}) Portable Text עם בלוקים מותאמים אישית
// schemas/blockContent.ts
import { defineType, defineArrayMember } from 'sanity'
export const blockContentType = defineType({
name: 'blockContent',
title: 'Block Content',
type: 'array',
of: [
defineArrayMember({
type: 'block',
styles: [
{ title: 'Normal', value: 'normal' },
{ title: 'H2', value: 'h2' },
{ title: 'H3', value: 'h3' },
{ title: 'Quote', value: 'blockquote' },
],
marks: {
decorators: [
{ title: 'Bold', value: 'strong' },
{ title: 'Italic', value: 'em' },
{ title: 'Code', value: 'code' },
],
annotations: [
{
name: 'link',
type: 'object',
fields: [
defineField({ name: 'href', type: 'url' }),
defineField({ name: 'blank', type: 'boolean', title: 'Open in new tab' }),
],
},
],
},
}),
// Встроенные изображения
defineArrayMember({
type: 'image',
options: { hotspot: true },
fields: [
defineField({ name: 'alt', type: 'string' }),
defineField({ name: 'caption', type: 'string' }),
],
}),
// Кастомный блок — цитата с автором
defineArrayMember({
type: 'object',
name: 'callout',
title: 'Callout',
fields: [
defineField({ name: 'text', type: 'text' }),
defineField({
name: 'type',
type: 'string',
options: {
list: ['info', 'warning', 'tip'],
layout: 'radio',
},
}),
],
}),
],
})
מסמך הגדרות Singleton
// schemas/siteSettingsType.ts
export const siteSettingsType = defineType({
name: 'siteSettings',
title: 'Настройки сайта',
type: 'document',
fields: [
defineField({
name: 'siteName',
type: 'string',
validation: r => r.required()
}),
defineField({
name: 'logo',
type: 'image'
}),
defineField({
name: 'favicon',
type: 'image'
}),
defineField({
name: 'socialLinks',
type: 'array',
of: [
defineArrayMember({
type: 'object',
fields: [
defineField({
name: 'platform',
type: 'string'
}),
defineField({
name: 'url',
type: 'url'
}),
],
})
],
}),
],
preview: {
select: {
title: 'siteName',
media: 'logo'
}
},
}) השוואה: סכמות מותאמות אישית מול תבניות מוכנות
| קריטריון | סכמות מותאמות אישית | תבניות מוכנות |
|---|---|---|
| מהירות פיתוח | 2–5 ימים עבור 4–6 סכמות | 1–2 ימים להתאמה |
| גמישות | שליטה מלאה | פונקציונליות מוגבלת |
| ביצועים | מותאם לסטאק שלכם | יכול להיות מוגזם |
סכמות מותאמות אישית מנצחות בגמישות פי 2-3 ועוזרות להימנע משאילתות N+1, דבר קריטי לביצועים. תבניות מוכנות חוסכות זמן בתחילה אך דורשות התאמה אישית למשימות אמיתיות.
מה כלול
- תיעוד: תיאור של כל הסכמות, קבוצות השדות והיחסים עם דיאגרמות.
- קוד מקור: קבצי TypeScript עם ולידציה מלאה ותצוגה מקדימה.
- גישה: תצורת Sanity Studio, תפקידים ואסימוני API.
- הדרכה: הוראות למנהלי תוכן כיצד להשתמש ב-Studio.
- תמיכה: שבועיים של שינויים חינם לאחר המסירה.
- תיאורי סכמות מפורטים עם תיעוד ברמת שדה.
תהליך עבודה
| שלב | מה אנו עושים | תוצאה |
|---|---|---|
| ניתוח | זיהוי טיפוסי תוכן, יחסים, דרישות ולידציה | מסמך עם רשימת סכמות |
| עיצוב | שרטוט גרף מסמכים ויחסים | דיאגרמת ER |
| פיתוח | כתיבת סכמות TypeScript, הגדרת קבוצות ותצוגה מקדימה | 4–6 סכמות עם ולידציה |
| בדיקות | בדיקת Studio, API, אינטגרציית פרונטאנד | רשימת בדיקות שעברו |
| פריסה | פריסת תצורה, הענקת גישה | Studio עובד |
לוח זמנים משוער
פיתוח 4–6 סכמות עם Portable Text, יחסים וולידציה — בין 2 ל-5 ימים. העלות מחושבת באופן פרטני לאחר ניתוח הדרישות שלכם. עלות טיפוסית לפיתוח 4–6 סכמות מותאמות אישית נעה בין €1,200 ל-€3,000, תלוי במורכבות. צרו קשר לייעוץ — אנו מעריכים את הפרויקט שלכם ביום אחד.
טעויות נפוצות ביצירת סכמות כוללות חוסר בקבוצות שדות, קינון אובייקטים עמוק מדי, התעלמות מולידציה ותצורת slug שגויה. הניסיון שלנו עוזר להימנע מבעיות אלו.







