Have you written a GROQ query that felt like Walter White cooking in an RV? Powerful, slightly dangerous, and held together with hope and duct tape. The moment your queries go from "simple" to "nested twelve levels deep with filters, joins, conditionals, projections, and vibes," you feel it. But GROQ isn't the problem. It's your architecture. Here's how to turn unmaintainable queries into code your future self won't want to git blame.
Modern GROQ query optimization
GROQ is Sanity's open-source query language, and it's basically what you get when you ask, "What if querying content didn't feel like wrestling a REST endpoint at 2 a.m.?" It lets you filter, sort, transform, and reshape data. You can filter October events, stitch together five document types, and reshape data before your frontend even wakes up, but at scale, it becomes the spiritual successor to "that one regex nobody wants to touch."
It's genuinely good at pulling together complex, multi-document structures and turning them into neat little JSON objects. For big datasets and intricate content models, GROQ feels like cheating.
The thing is, GROQ gives you total freedom. You can ask it precisely what you want, and it gives you exactly that. No overfetching, no REST gymnastics, no GraphQL boilerplate sermons. Just raw, precise querying. Which is great… right up until your query becomes a 70-line monster that nobody wants to maintain.
GROQ complexity starts with scaling
As we said before, the moment you scale past a couple of tidy queries, things go from "elegant data language" to "who wrote this and are they okay?" You end up with dense, repetitive query chains that stretch across half your screen, nested filters you're scared to touch, and projections so long they need their own scroll bar. Modify one line and you feel like you're making a Jenga move in front of your entire engineering team.
We obviously don't work like this, but you definitely don't want this kind of chaos spreading across your repo or onboarding a new developer into this cinematic universe of pain.
Consider this nightmare scenario:
Now, this query is extremely difficult to comprehend, debug, and maintain. But we are here to say that there's a better way.
The modern solution: fragment-based architecture
GROQ is just text. Plain JavaScript strings, which means we can actually structure them. Once that clicked, we rebuilt our entire approach around fragments. Small, and reusable building blocks that turn sprawling, uneditable query monsters into clean, modular pieces you can rearrange, extend, and fix without sweating. Here's the exact pattern or cheat sheet we now use to keep GROQ readable, scalable, and onboarding-friendly:
1. Start with atomic fragments
Begin by identifying the smallest reusable units in your queries. These become your atomic fragments:
Each of these fragments does one thing well. imageFragment handles image resolution with alt text fallbacks and blur data. customLinkFragment resolves internal and external links with smart routing. buttonsFragment maps button arrays into clean, frontend-ready objects. They're small, readable, and you'll reuse them everywhere.
2. Build composite fragments
Combine atomic fragments into more complex, reusable components:
See how richTextFragment composes markDefsFragment and imageFragment? Each layer builds on the one below. You never repeat yourself, and when you update imageFragment, every query that uses it gets the fix automatically.
3. Create block-level fragments
For page builders and complex content structures, create block-level fragments that handle entire components:
Each block uses the _type == "..." => { ... } conditional projection pattern. This means a single pageBuilder[] array query can handle every block type polymorphically, no frontend switch statements needed.
4. Compose page-level queries
Finally, combine all fragments into clean, readable page queries:
Look at that queryBlogSlugPageData. It pulls in author data, images, rich text, FAQs, and an entire page builder, and it's still readable. You can see exactly what data you're fetching at a glance. Try doing that with a 120-line inline query.
Best practices for production GROQ
If you've made it this far, you already know GROQ can either be a joy or a long-term liability. A few production habits save you from debugging nightmares six months later when no one remembers who wrote that query or why.
Type-safe query definitions
Do yourself a favour and always wrap queries with defineQuery from next-sanity. It keeps your types in sync, catches silly mistakes before they hit prod, and stops your editor from acting like it has no idea what data you're asking for.
Smart data transformation
GROQ gives you built-ins that can do half your cleanup before the data even reaches your frontend. Use them. Format dates, slice arrays, merge fields, and filter edge cases right inside the query. It keeps your components simple and stops you from writing 14 helper functions called formatSomething().
Conditional block rendering
If your frontend is full of if/else to decide which block should show up, you're doing extra work for no reason. Push that logic into GROQ. Let the query decide what gets returned (and what doesn't), so your components only render what actually matters.
Always explicit projections
The ... operator feels convenient, but what happens when shipping fields you didn't even know existed? Don't do that to yourself (or your build times). List every field you actually need. It keeps queries predictable, keeps payloads lean, and stops accidental "why is this field here?" moments.
Always use parameters, not string interpolation
String-interpolating variables into a GROQ query is how security bugs and broken queries sneak in undetected. Use $params every single time. It keeps your queries safe, predictable, and way easier to debug. If you ever catch yourself writing ${id} for a runtime value, please, please stop.
Use consistent naming conventions
Name your fragments so future-you (or your team) can understand them. Stick to a simple rule like suffixing reusable bits with Fragment and full components with Block. Just remember, the name should be as descriptive as possible.
Organize by complexity
Keep atomic pieces together, bundle mid-level components separately, and stash full page-level blocks in their own folder. Or at minimum, keep them in order within a single file, atomic at the top, page-level exports at the bottom.
Use TypeScript comments
Use /* groq */ before your queries so your editor highlights them instead of treating them like sad multiline strings. It makes scanning, debugging, and refactoring far easier.
Performance benefits
Once you switch to a fragment-based setup, the performance difference is noticeable.
Instead of copy-pasting the same projection across seven queries (we've all done it, no shame), fragments keep everything DRY and predictable. That means the CDN can cache your responses properly instead of treating every slightly-different query as a new snowflake. You also stop over-fetching half your dataset "just in case," because each fragment forces you to be explicit about what you need. Smaller queries mean faster responses, which leads to fewer Slack messages asking "why is the page still loading?"
Development gets faster, too. When your queries are modular, page-level builds become Lego. Snap pieces together, ship, move on with your day.
What you should actually do next
If there's one thing to take away from this entire breakdown, it's this: stop treating GROQ like a dumping ground for giant, unreadable queries and start treating it like a system you can actually maintain without sacrificing your weekends.
Fragment-based architecture gets you readable queries, less duplication, and smaller payloads. Your teammates will actually be able to onboard into your codebase without a guided tour.
Start small. Build atomic fragments. Compose them into blocks. Then treat page-level queries like Lego builds instead of archaeological digs. And if you want help untangling your current GROQ situation or setting this up the right way from day one, you know where to find us.
Get in touch
Book a meeting with us to discuss how we can help or fill out a form to get in touch




