שאילתות GROQ ב-Sanity: מהיסודות ועד לאופטימיזציה מתקדמת
תארו לעצמכם: אתם בונים אתר headless עם Sanity ו-Next.js. מודל התוכן שלכם גדל—6 סוגי מסמכים, קשרים מורכבים, אזורים דינמיים. בעמוד הנחיתה הראשון, אתם נתקלים בתסכול: שאילתות N+1, שליפות מקוננות, ובלוק "מאמרים קשורים" שנטען ב-7 שניות. נתקלנו בזה בפרויקט פורטל מדיה גדול: זמן טעינה של 12 שניות, 47 בקשות נפרדות. הפתרון היה שאילתת GROQ אחת שקיצצה את הזמן ל-0.8 שניות. נשמע מוכר? טיפלנו בזה יותר מפעם אחת.
GROQ (Graph-Relational Object Queries) הוא לא REST, לא SQL, ולא GraphQL. הוא גמיש יותר מ-REST למבנים מורכבים: projections, חיבורים דרך ->, בחירות מותנות, אגרגציות—הכל בשאילתה אחת ללא בעיות N+1. הצוות שלנו, עם ניסיון של למעלה מחמש שנים ב-Sanity, כיוון עשרות פרויקטים, וקיצץ את עומס ה-API עד פי שלושה. במאמר זה, נראה לכם איך לכתוב שאילתות GROQ כראוי, להימנע מטעויות נפוצות, ולמטב מהירות.
למה GROQ במקום REST או GraphQL?
Sanity מציע API מסוג HTTP מחוץ לקופסה, אבל נקודות קצה מותאמות אישית לכל עמוד מובילות לכאוס. GROQ נותן לכם תחביר אחיד לכל שליפה. השוו:
| קריטריון | GROQ | REST API | GraphQL |
|---|---|---|---|
| שאילתות לעמוד | 1 (מורכבת) | 5–15 (מרובות) | 1–3 (אבל N+1 עם pagination) |
| חיבור (resolve) | מובנה -> |
דורש שאילתות נפרדות | דורש batch loading |
| הקרנות מותנות | מובנה _type == "..." => |
אין, חייבים לסנן בצד הלקוח | כן, דרך fragments |
| אגרגציות (count, unique) | פונקציות מובנות | אין, צריך server hooks | כן, אבל מורכב יותר |
GROQ מהיר פי 2–3 בשליפת עמודים עם סוגי בלוקים מרובים—הוכח בפרויקטים שלנו.
איך לבצע נכון שאילתות Join ב-GROQ?
חיבורים ב-GROQ נעשים עם אופרטור ה-resolve ->. הוא מחליף JOINs רלציוניים ומאפשר לשלוף מסמכים קשורים ללא שאילתות נוספות. צעד אחר צעד:
- זיהוי שדה ההפניה (לדוגמה,
authorמסוגreference). - שימוש ב-
->אחרי השדה:author->{name}. - הגבלת שדות עם projection כדי להימנע מטעינת נתונים מיותרים.
שאילתה אחת מחזירה פוסטים יחד עם נתוני המחבר:
*[_type == "post"]{ title, "author": author->{name, "avatar": image.asset->url} } הגבילו את עומק ה-resolve—שתיים עד שלוש רמות מספיקות בפועל.
מקרה בוחן: אופטימיזציה של זמן טעינת בלוג
לקוח הגיע אלינו עם בעיה: עמוד הבלוג שלו על Sanity + Next.js נטען ב-12 שניות. גילינו שעבור רשימת המאמרים, בוצעו 47 שאילתות נפרדות (כל פוסט שלף מחבר, קטגוריות, מאמרים קשורים ומטא-דאטה). הפתרון היה שאילתת GROQ אחת עם resolve ו-pagination:
*[_type == "post" && status == "published"] | order(publishedAt desc) [$start...$end] {
_id,
title,
"slug": slug.current,
publishedAt,
"author": author->{
name,
"avatar": image.asset->url
},
"categories": categories[]->{
title,
"slug": slug.current
},
"excerpt": string::slice(pt::text(body), 0, 200)
}
count(*[_type == "post" && status == "published"])
תוצאה: שאילתה אחת, 0.8 שניות במקום 12. בנוסף, בונוס—ספירת המאמרים הכוללת ל-pagination. אנחנו בונים אופטימיזציות כאלה לתוך סט השאילתות הסטנדרטי שלנו.
מה כלול בהקמת שאילתות GROQ
אנחנו מציעים פיתוח turnkey—מביקורת ועד תיעוד. החבילה כוללת:
- ניתוח מודל התוכן שלכם וזיהוי צווארי בקבוק (N+1, שאילתות מיותרות)
- עיצוב שאילתות אוניברסליות ל-4–6 סוגי עמודים: דפי נחיתה, רשימות, כרטיסי פרטים, חיפוש
- יישום עם טיפוסי TypeScript—כל שאילתה עטופה בתג groq שמחזיר את הטיפוס הנכון
- אופטימיזציה דרך projections, resolve ואגרגציות—payload מינימלי
- אינטגרציה עם Sanity Vision לניפוי באגים ובדיקות
- הדרכת צוות—תיעוד ותבניות נמסרים
הסט הסטנדרטי מכסה תחביר בסיסי, שאילתות עם פרמטרים, Portable Text, pagination, חיפוש טקסט מלא עם Algolia (אם צריך), וחיפושים הפוכים (refs). הכל נבדק על 30+ פרויקטים.
טעויות נפוצות בכתיבת GROQ
- שרשור מחרוזות במקום פרמטרים—סיכון ל-injection, ללא caching. תמיד השתמשו ב-
*[_type == "post"]{ title, "author": author->{name, "avatar": image.asset->url} }. - עומק resolve מופרז—יותר משלוש רמות פוגע בביצועים. היצמדו ל-2–3.
- התעלמות מ-
*[_type == "post" && status == "published"] | order(publishedAt desc) [$start...$end] { _id, title, "slug": slug.current, publishedAt, "author": author->{ name, "avatar": image.asset->url }, "categories": categories[]->{ title, "slug": slug.current }, "excerpt": string::slice(pt::text(body), 0, 200) } count(*[_type == "post" && status == "published"])בבדיקת שדות—שדות יכולים להיות null ב-Sanity. - חוסר pagination ברשימות מעל 100 פריטים. השתמשו ב-
$variable. - שכחת ה-count—החזירו גם מערך וגם ספירה בו-זמנית.
דוגמה: סט שאילתות מלא (עם טיפוסים)
import { createClient } from '@sanity/client'
import { groq } from 'next-sanity'
const postQuery = groq`
*[_type == "post" && slug.current == $slug][0] {
_id,
title,
"slug": slug.current,
publishedAt,
body,
"author": author->{ name, "image": image.asset->url },
"relatedPosts": *[_type == "post" && references(^.categories[]._ref) && _id != ^._id] | order(publishedAt desc) [0...3] {
title,
"slug": slug.current
}
}
`
type PostResult = {
_id: string
title: string
slug: string
publishedAt: string
body: any[]
author: { name: string; image: string }
relatedPosts: { title: string; slug: string }[]
}
const post = await client.fetch<PostResult>(postQuery, { slug: params.slug })תיעוד מלא של GROQ זמין באתר הרשמי של Sanity. אם אתם רוצים לזרז פיתוח—המהנדסים שלנו מוכנים לעזור בהקמה. צרו קשר כדי לדון בפרויקט שלכם ולמצוא את הפתרון האופטימלי. קבלו ייעוץ ממהנדס Sanity.
לוח זמנים ועלות
פיתוח סט שאילתות אורך יום עד שלושה ימים תלוי במורכבות המודל. העלות נקבעת באופן אישי. אנחנו לא מסתירים מספרים: פנו אלינו—נעריך את הפרויקט שלכם ביום עסקים אחד. אנחנו מבטיחים שהשאילתות יהיו מותאמות ל-Core Web Vitals ולא יגרמו לבעיות N+1.







