Why am I getting duplicate records in my Sanity query with projection?

6 replies
Last updated: Nov 29, 2025
For what reason could I be potentially receiving duplicate records in my query like so
0:Object
_createdAt:"2021-04-21T16:35:41Z"
_type:"page"
_updatedAt:"2021-05-07T20:10:51Z"
pageId:"4b4a7270-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
routeLabel:"testing dynamic"
slug:"sub/directory"
1:Object
_createdAt:"2021-04-21T16:35:41Z"
_type:"page"
_updatedAt:"2021-05-07T20:10:51Z"
pageId:"4b4a7270--xxxx-xxxx-xxxx-xxxxxxxxxxxx"
routeLabel:"testing dynamic"
slug:"sub/directory"
If my query looks like this.

*[_type == "route" && slug_custom_format.current != "index"]{
    routeLabel,
    "slug": slug_custom_format.current,
    ...page->{
      _createdAt,
      "pageId": _id,
      _type,
      _updatedAt,
    } 
  } | order(_updatedAt desc)
A bit stumped as to why this is happening. There are no drafts of the document.
AI Update

Based on your query structure, you're likely seeing duplicate records because of how the spread operator (...page->) works with references and the raw perspective.

The issue is that when you query *[_type == "route"] and then spread properties from a referenced page document, if that page has both a published version and a draft version (even if you don't see drafts in the Studio), your query might be returning the same route multiple times - once for each state of the referenced page document.

Here's what's happening:

  1. Your query fetches all route documents
  2. For each route, it follows the reference to page and spreads its properties
  3. If the referenced page exists in multiple states (draft and published), the query may be evaluating differently for each state

Solutions:

Option 1: Use the published perspective (recommended for production)

Add a perspective parameter to your API request to only return published documents:

// In your client configuration or query
const query = `*[_type == "route" && slug_custom_format.current != "index"]{
    routeLabel,
    "slug": slug_custom_format.current,
    ...page->{
      _createdAt,
      "pageId": _id,
      _type,
      _updatedAt,
    } 
  } | order(_updatedAt desc)`

// Fetch with published perspective
client.fetch(query, {}, { perspective: 'published' })

Option 2: Filter out draft references in your GROQ query

Explicitly exclude drafts in your query:

*[_type == "route" && slug_custom_format.current != "index" && !(_id in path("drafts.**"))]{
    routeLabel,
    "slug": slug_custom_format.current,
    ...page->{
      _createdAt,
      "pageId": _id,
      _type,
      _updatedAt,
    } 
  } | order(_updatedAt desc)

Option 3: Use defined() to ensure valid references

Sometimes references can cause duplication if they're undefined:

*[_type == "route" && slug_custom_format.current != "index" && defined(page)]{
    routeLabel,
    "slug": slug_custom_format.current,
    ...page->{
      _createdAt,
      "pageId": _id,
      _type,
      _updatedAt,
    } 
  } | order(_updatedAt desc)

The perspectives documentation explains how different perspectives affect query results. Even if you don't see drafts in the Studio, they may exist in your Content Lake, and the default raw perspective returns both states.

For production environments, always use the published perspective to avoid these kinds of duplicate results.

Show original thread
6 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?