Architecting Decoupled Content Delivery with Sanity and GROQ
Learn how to use GROQ projections in Sanity.io to eliminate over-fetching, reduce payload sizes, and secure your content delivery boundaries.
27 Sept 2025, 09:24 UTC

The Problem: Over-fetching in Headless Architectures
\nIn traditional headless CMS setups, APIs often return entire document objects. When a frontend only needs a title and a thumbnail, but the API returns a 50KB JSON object containing full body text, metadata, and internal IDs, the result is increased latency and wasted bandwidth. This “over-fetching” becomes a critical bottleneck as content scales.
\nThe solution is to move the data transformation from the client to the server. By using GROQ (Graph-Relational Object Query), you can reshape the data at the Content Lake level, ensuring the client receives only the specific fields required for the current view.
\nThe Minimal Design for Decoupled Delivery
\nThe smallest suitable design for a decoupled delivery system consists of three components: the Content Lake (centralized JSON storage), a GROQ projection (the server-side filter), and a client-side fetcher using an API token.
\nUnlike GraphQL, which requires a predefined schema for every query, GROQ allows for a functional approach. You can filter, join, and project data without modifying the backend schema, making it ideal for rapidly evolving frontend requirements.
\nExample: Projecting a Product Card
\nIf you have a product document that references an author document, a standard fetch would require two API calls or a massive nested object. A GROQ projection handles this in one request by “flattening” the relationship.
// Run this in the Sanity Studio Vision plugin\n*[_type == \"product\"] {\n \"productName\": title,\n \"price\": price,\n \"authorName\": author->name,\n \"thumbnail\": image.asset->url\n}\n\nAnalysis of this configuration:\n
- \n
*[_type == \"product\"]: Filters the dataset to only include product documents. \n \"productName\": title: Renames the field for the frontend, decoupling the API key from the UI variable. \n author->name: Follows a reference (the->operator) to the author document and retrieves only the name. \n
Trust and Data Boundaries
\nData security in Sanity is managed through API tokens and CORS (Cross-Origin Resource Sharing). To maintain a secure boundary:
\n- \n
- Read-Only Tokens: Public-facing frontends should use a token with read-only permissions. Never embed a write-token in client-side code. \n
- CORS Restrictions: Limit API access to specific domains (e.g.,
your-app.vercel.app) to prevent unauthorized third-party sites from scraping your Content Lake. \n - Projection as a Filter: Use GROQ projections to exclude sensitive internal fields (like
internalNotesordrafts) before the data ever leaves the server. \n
Operational Checks and Performance
\nTo ensure the architecture remains performant, monitor two primary metrics: Query Execution Time and Documents Scanned.
\nInefficient queries—such as those using wildcards on large datasets without filters—increase the number of documents scanned, which can lead to slower response times and potential rate-limiting.
\nVerification Steps
\n- \n
- Vision Plugin: Execute the query in the Sanity Studio Vision tool. Check the execution time listed in the response metadata. \n
- Network Inspection: Open the browser Developer Tools Network tab. Compare the size of the JSON payload when using a projection versus fetching the whole document. The projection should significantly reduce the
Content-Length. \n
Failure Modes and Design Pivots
\nWhile GROQ is powerful, it has physical limits based on the Content Lake's architecture.
\nCommon Failure Points
\n- \n
- Deep Nesting: Following too many references (e.g.,
A -> B -> C -> D) in a single query can lead to timeout errors or exceed the maximum response size. \n - Rate Limiting: Excessive polling or unoptimized loops in the frontend can exhaust API quotas. Implement a caching layer (like an ISR or CDN) between the client and Sanity. \n
When to Change the Design
\nThe current GROQ-based design is optimal for most content-driven sites. However, you should pivot to a dedicated graph database or implement materialized views if:
\n- \n
- Your data model becomes highly relational with thousands of cross-references per document. \n
- You require complex recursive queries that exceed GROQ's depth limits. \n
- The latency of server-side joins becomes unacceptable for your specific UX requirements. \n
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.