How to resolve references for split pane preview in Sanity Studio?
For implementing a split pane preview in Sanity Studio with resolved references, you'll want to use a combination of custom Component Views and GROQ queries with the reference operator (->). Let me walk you through the best approach.
Understanding SanityDocument
SanityDocument is a TypeScript type that represents any document in your Sanity Content Lake. When you run sanity typegen generate, it creates type definitions for all your schema types. The base SanityDocument type typically includes core fields like _id, _type, _createdAt, _updatedAt, and _rev.
If you're seeing SanityDocument in sanity-plugin-utils but can't figure out what it is, it's likely a generic type parameter. You'll want to use your own generated types from sanity typegen instead.
Implementing Split Pane Preview with Resolved References
Here's the recommended approach using Component Views:
1. Set up the Structure with Component View
import { StructureBuilder as S } from 'sanity/structure'
import PreviewPane from './components/PreviewPane'
export const structure = (S) =>
S.list()
.title('Content')
.items([
S.listItem()
.title('Your Document Type')
.child(
S.documentTypeList('yourDocType')
.child((documentId) =>
S.document()
.documentId(documentId)
.schemaType('yourDocType')
.views([
S.view.form(),
S.view.component(PreviewPane).title('Preview')
])
)
)
])2. Fetch Data with Resolved References
In your PreviewPane component, use the useClient hook to fetch the document with references resolved using GROQ's reference operator (->):
import { useEffect, useState } from 'react'
import { useClient } from 'sanity'
export default function PreviewPane(props) {
const { document } = props
const client = useClient({ apiVersion: '2024-01-01' })
const [resolvedDoc, setResolvedDoc] = useState(null)
useEffect(() => {
const query = `*[_id == $id][0]{
...,
// Resolve single reference
author->,
// Resolve array of references
categories[]->,
// Resolve nested references with specific fields
relatedPosts[]->{
title,
slug,
author->
}
}`
client.fetch(query, { id: document.displayed._id })
.then(setResolvedDoc)
}, [document.displayed._id, client])
if (!resolvedDoc) return <div>Loading preview...</div>
return (
<div style={{ padding: '2rem' }}>
<h2>{resolvedDoc.title}</h2>
<p>Author: {resolvedDoc.author?.name}</p>
{/* Render your preview with resolved data */}
</div>
)
}3. Handle Real-time Updates
For a more robust solution that updates in real-time as you edit, you can use the client's listen() method:
useEffect(() => {
const query = `*[_id == $id][0]{ ..., author->, categories[]-> }`
const subscription = client.listen(query, { id: document.displayed._id })
.subscribe((update) => {
if (update.result) {
setResolvedDoc(update.result)
}
})
// Initial fetch
client.fetch(query, { id: document.displayed._id })
.then(setResolvedDoc)
return () => subscription.unsubscribe()
}, [document.displayed._id, client])Key Points About Reference Resolution
The -> operator is powerful for resolving references in GROQ:
- Single references:
author->resolves to the full author document - Array references:
categories[]->resolves all category references - Selective fields:
author->{name, bio}gets only specific fields - Nested resolution:
relatedPosts[]->{ title, author-> }resolves multiple levels
Keep in mind that each -> performs a subquery internally, so be mindful of performance when resolving deeply nested or large arrays of references.
TypeScript Types
If you want full type safety, generate types with sanity typegen:
npx sanity@latest schema extract
npx sanity@latest typegen generateThen import and use your generated types:
import type { YourDocType } from './sanity.types'
const [resolvedDoc, setResolvedDoc] = useState<YourDocType | null>(null)This approach gives you a clean split pane view with fully resolved references, updating in real-time as you edit your document.
Show original thread3 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.