Best approach for retrieving translations in GROQ queries from frontend?

8 replies
Last updated: Jan 28, 2026
Hey, is there a best approach for dealing with retrieving translations when running groq queries from the frontend?
Some context to this question
👇• We are using the
intl plugin for sanity to handle localization for our app. • We had to do some digging in order to retrieve blog articles data + associated translations. In our search,
this article has been sort of a guiding beacon.
WHAT WE'VE ATTEMPTED:• WHAT WORKS WELL
👍 The groq query highlighted in the above article works well when we use it in our sanity studio. The query's reference to
__i18n_lang && __i18n_refs
is represented by
_lang && _langRefs
for us as we've configured it so. So the query ran on studio looks something like this:
*[_type == "blog"]{
  _id,
  _lang,
  _langRefs,
  !(_id match "i18n*") => {
  "translations": *[_id in path("i18n." + ^._id + ".*")]
  },
  _id match "i18n*" => {
  "translations":
    *[^._id in _langRefs[].ref._ref]{_id, _lang} + 
    *[^._id in _langRefs[].ref._ref][0]{
      "matches": *[_id in path("i18n." + ^._id + ".*")]{_id, _lang}
    }.matches
  },

In our Sanity Studio, the results from this query return a blog article + an array of its
translations
filled with data of the translated blog articles.
• WHAT DOESN'T WORK SO WELL
👎 When attempting the same query in our frontend Next.js environment, the results return a blog article however this time with an empty array of
translations
----

I feel like there is something we've underthought or overlooked in our approach here. I'm happy for any guidance that will help me get on the right path. Thank you
🙏
AI Update

I can see you're hitting a frustrating issue where your translation query works in Studio but returns empty translations on the frontend. This is a common problem when working with document-level localization patterns, and the root cause is almost certainly related to API perspectives.

The Core Issue: Different Perspectives Between Studio and Frontend

Your Sanity Studio's Vision tool and your frontend Next.js app are likely querying different "views" of your content. The API version 2025-02-19 introduced a breaking change where the default perspective switched from raw to published. This means:

  • Studio Vision: Often uses the raw perspective by default, which shows all documents including drafts
  • Your frontend: If using a recent API version without explicitly setting a perspective, it defaults to published, which only shows published documents

Since your path("i18n." + ^._id + ".*") pattern is looking for documents with specific ID patterns, if those translation documents aren't published (they're only drafts), they won't appear in your frontend results.

Quick Fix: Set Your Perspective Explicitly

Try explicitly setting the perspective in your frontend Sanity client:

import { createClient } from '@sanity/client'

const client = createClient({
  projectId: 'your-project-id',
  dataset: 'your-dataset',
  apiVersion: '2024-01-01', // or your current version
  perspective: 'previewDrafts', // Shows published + drafts, prioritizing drafts
  useCdn: false, // Disable CDN while debugging
})

The previewDrafts perspective will give you both published and draft documents, prioritizing drafts when both exist. You could also try perspective: 'raw' which shows all documents.

Verify Translation Document Publication Status

Your translation documents (those with i18n.* IDs) might exist only as drafts. To debug this, run a simple query from your frontend:

const result = await client.fetch('*[_id match "i18n.*"]')
console.log(result)

If this returns empty but works in Studio, you've confirmed it's a perspective/publication issue. Make sure your translation documents are actually published (not just saved as drafts). In Sanity, draft documents have a drafts. prefix in their _id, and they won't appear in queries using the published perspective.

Systematic Debugging Steps

  1. Verify document visibility: Run *[_id match "i18n.*"] from your frontend with different perspectives:

    • With perspective: 'published' (current default for newer API versions)
    • With perspective: 'previewDrafts' (published + drafts, with drafts prioritized)
    • With perspective: 'raw' (all documents)
  2. Check publication status in Studio: Go to your Studio and verify that your translation documents (the ones with i18n.* IDs) are actually published. Look for the "Publish" button - if it's available, the document is still a draft.

  3. Disable CDN caching: Set useCdn: false temporarily to ensure you're not hitting stale cached results.

  4. Compare Studio Vision configuration: Your Studio's Vision tool might be using a different perspective than your frontend. Check if there's a perspective selector in Vision.

  5. Test with simpler queries first: Before running the complex translation query, make sure you can see the basic documents with *[_type == "blog"] and the translation documents with *[_id match "i18n.*"].

About Your Query Pattern

The approach you're using with path("i18n." + ^._id + ".*") relies on document ID pattern matching, which is sensitive to perspective and publication state. The pattern itself is solid, but it requires that the documents it's looking for are visible in the perspective you're querying with.

For immediate debugging:

// In your frontend client configuration
const client = createClient({
  // ... your existing config
  perspective: 'previewDrafts',
  useCdn: false,
})

For production: Once you've confirmed the issue, decide on your approach:

  • Option A: Ensure all translation documents are properly published, then use perspective: 'published' in production
  • Option B: Use perspective: 'previewDrafts' if you need to show draft translations in your frontend
  • Option C: Adjust your content workflow to better match your querying needs

The key insight here is that Studio and your frontend are querying different views of your content due to perspective differences. Studio's Vision tool often shows all documents (similar to raw perspective), while your frontend client—especially if using a recent API version—defaults to only published documents. This explains why you see translations in Studio but not in your Next.js app.

Show original thread
8 replies

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?