Optimizing Sanity Content Lake Retrieval with GROQ Projections
Learn how to use GROQ projections and dereferencing in Sanity to eliminate over-fetching and deliver lean, UI-ready JSON to your frontend.
13 Oct 2025, 07:37 UTC

The Problem: Over-fetching and API Latency
In a headless architecture, requesting an entire document from a CMS often returns unnecessary metadata, internal IDs, and deeply nested references that the frontend doesn't need. This "over-fetching" increases payload size, slows down page loads, and complicates the data mapping logic in your frontend components.
The solution is to move the data transformation logic from the client-side code to the GROQ (Graph-Relational Object Query Language) query. By using projections, you can reshape the JSON response to match your UI requirements exactly before the data even leaves the Sanity Content Lake.
Prerequisites
- A Sanity project with a configured Studio.
- At least two related content types (e.g.,
authorandpost) where the post references the author. - The Vision plugin installed in the Sanity Studio for query testing.
Structuring Schemas for Relational Data
To retrieve filtered data, your schemas must first establish a relationship. In Sanity, this is done using the reference type. For example, a post schema should include a field that points to an author document.
// post.js schema snippet
{
name: 'author',
title: 'Author',
type: 'reference',
to: [{type: 'author'}]
}Implementing Precise Projections
Instead of fetching the entire post document, use a projection (the curly braces {} following the filter) to select only the required fields. To handle the reference to the author, use the dereferencing operator ->, which tells Sanity to follow the reference and return the linked document's data.
Example: Fetching a Post with Author Details
Run this query in the Sanity Studio Vision plugin to verify the output structure:
*[_type == "post" && published == true][0] {
title,
"slug": slug.current,
"authorName": author->name,
"authorBio": author->bio
}Analysis of the Query
*[_type == "post" && published == true][0]: Filters for the first published post."slug": slug.current: Flattens the slug object into a simple string, removing the need fordata.slug.currentin your frontend."authorName": author->name: Dereferences the author ID and projects only thenamefield into a custom key.
Diagnostic Checks and Verification
To ensure your query is performing optimally and returning the correct shape, follow these verification steps:
| Check | Method | Expected Result |
|---|---|---|
| Data Shape | Vision Plugin | JSON matches the frontend interface props exactly. |
| Payload Size | Browser Network Tab | Response body contains no _rev or _type fields unless explicitly requested. |
| CDN Cache | Request Header | x-sanity-cache: HIT indicates the request is served from the edge. |
Performance Limitations
While GROQ is powerful, deeply nested dereferencing (e.g., post->category->parent->name) can increase query execution time. If you find yourself dereferencing more than three levels deep, consider flattening your schema or creating a separate query for the nested data.
Rollback and Data Safety
Because GROQ queries are read-only operations, they do not change the state of your Content Lake. However, if you modify your schema definitions to support new queries, remember that Sanity does not automatically migrate existing data. To revert a schema change:
- Revert the JavaScript/TypeScript schema file to the previous version.
- Restart the Studio development server.
- If data was manually migrated via script, you must run a reverse migration script to restore the original field formats.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.