GROQ Queries in Sanity: From Basics to Advanced Optimization

GROQ Queries in Sanity: From Basics to Advanced Optimization

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:

Frequently Asked Questions

Latest works

  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1285
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1241
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    982
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    1033
  • image_website-sbh_0.webp
    Website development for SBH Partners
    1104
  • image_website-_0.webp
    Website development for Red Pear
    554

GROQ Queries in Sanity: From Basics to Advanced Optimization

Picture this: you’re building a headless site with Sanity and Next.js. Your content model has grown—6 document types, complex relationships, dynamic zones. On the very first promo landing page, you hit frustration: N+1 queries, nested fetches, and a “related articles” block that takes 7 seconds to load. We faced this on a major media portal project: 12-second load time, 47 separate requests. The solution was a single GROQ query that cut time to 0.8 seconds. Sound familiar? We’ve handled this more than once.

GROQ (Graph-Relational Object Queries) is not REST, not SQL, not GraphQL. It’s more flexible than REST for complex structures: projections, joins via ->, conditional selections, aggregations—all in one query without N+1 issues. Our team, with over five years of Sanity experience, has tuned dozens of projects, cutting API load by up to three times. In this article, we’ll show you how to write GROQ queries properly, avoid common mistakes, and optimize speed.

Why GROQ Instead of REST or GraphQL?

Sanity offers an HTTP API out of the box, but custom endpoints for every page lead to chaos. GROQ gives you a unified syntax for any retrieval. Compare:

Criterion GROQ REST API GraphQL
Queries per page 1 (complex) 5–15 (multiple) 1–3 (but N+1 with pagination)
Join (resolve) Built-in -> Requires separate queries Requires batch loading
Conditional projections Built-in _type == "..." => None, must filter client‑side Yes, via fragments
Aggregations (count, unique) Built-in functions None, need server hooks Yes, but more complex

GROQ is 2–3 times faster when fetching pages with multiple block types—proven on our projects.

How to Properly Execute Join Queries in GROQ?

Joins in GROQ are done with the resolve operator ->. It replaces relational JOINs and lets you pull related documents without extra queries. Step by step:

  1. Identify the reference field (e.g., author of type reference).
  2. Use -> after the field: author->{name}.
  3. Limit fields with a projection to avoid loading unnecessary data.

A single query returns posts together with author data:

*[_type == "post"]{ title, "author": author->{name, "avatar": image.asset->url} } 

Limit resolve depth—two to three levels is enough in practice.

Case Study: Optimizing Blog Load Time

A client came to us with a problem: their blog page on Sanity + Next.js loaded in 12 seconds. We found that for the article list, 47 separate queries were executed (each post fetched author, categories, related articles, and metadata). The solution was a single GROQ query with resolve and 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"]) 

Result: 1 query, 0.8 seconds instead of 12. Plus a bonus—total article count for pagination. We build such optimizations into our standard query set.

What's Included in GROQ Query Setup

We offer turnkey development—from audit to documentation. The package includes:

  • Analysis of your content model and identification of bottlenecks (N+1, redundant queries)
  • Design of universal queries for 4–6 page types: landing pages, lists, detail cards, search
  • Implementation with TypeScript typing—each query wrapped in a groq tag returning the correct type
  • Optimization via projections, resolve, and aggregations—minimal payload
  • Integration with Sanity Vision for debugging and testing
  • Team training—documentation and templates handed over

The standard set covers basic syntax, parameterized queries, Portable Text, pagination, full-text search with Algolia (if needed), and reverse lookups (refs). All tested on 30+ projects.

Common GROQ Writing Mistakes
  • String concatenation instead of parameters—risk of injection, no caching. Always use $variable.
  • Excessive resolve depth—more than three levels kills performance. Stick to 2–3.
  • Ignoring defined() when checking fields—fields can be null in Sanity.
  • Missing pagination on lists over 100 items. Use [$start...$end].
  • Forgetting the count—return both array and count simultaneously.

Example: Full Query Set (with Typing)

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 }) 

Full GROQ documentation is available on the official Sanity site. If you want to speed up development—our engineers are ready to help with setup. Contact us to discuss your project and find the optimal solution. Get a consultation from a Sanity engineer.

Timeline and Cost

Developing a set of queries takes one to three days depending on model complexity. Cost is determined individually. We don’t hide numbers: reach out—we’ll assess your project in one business day. We guarantee the queries will be optimized for Core Web Vitals and won’t cause N+1 issues.