Potential reasons for receiving duplicate records in a query and how to debug the issue.
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:
- Your query fetches all
routedocuments - For each route, it follows the reference to
pageand spreads its properties - 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.
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.