An opinionated guide to Sanity Studio
Sanity Studio is an incredibly flexible tool with near limitless customisation. Here's how I use it.
Go to An opinionated guide to Sanity StudioWhen setting up Visual Editing, you may create unique components to perform live queries. However, it might be more convenient in a larger application to reuse a single wrapper component that can perform a query and pass live-updating content to any child component.
If you’re setting up Sanity's interactive live preview and Presentation for the first time, start with one of these guides:
The guides above walk through how to set up these client libraries:
Assumptions:
The code examples below are taken from a Next.js App router project, and some include the ‘use client’
directive. You can safely remove these lines from the code examples for frameworks that do not yet support React Server Components.
LiveQueryWrapper
which will take the initial data you initially fetch server-side. Along with the query and parameters that were used for that initial data.data
.Traditionally, you could use “render props” and pass a rendering function to render either published or preview content conditionally. However, passing functions from server to client is not currently possible with React Server components. The technique shown in this guide relies on conditionally loaded components that prop-drill published or preview content to their child component.
The "Course Platform Demo" is a Sanity Studio and Next.js App router application that uses this method to reuse the LiveQueryWrapper
component in every route to make each unique layout automatically updated with live preview.
Note: It is predominately a demonstration of internationalization strategies with Sanity and is not intended as a starter template for new projects.
The magic of this approach is handled by @radix-ui/react-slot
, which enables components to pass props to their direct children, no matter what they are.
Install it to your front-end project:
npm install @radix-ui/react-slot
Create a new component file for LiveQueryWrapper
, which will wrap the component you use to render Sanity content:
// ./components/LiveQueryWrapper.tsx
import {Slot} from '@radix-ui/react-slot'
import {QueryParams} from '@sanity/client'
import {QueryResponseInitial} from '@sanity/react-loader'
import {PropsWithChildren} from 'react'
import {LiveQueryData} from '@/components/LiveQueryData'
type PreviewWrapperProps<T> = PropsWithChildren<{
initial: QueryResponseInitial<T>
isEnabled?: boolean
query?: string
params?: QueryParams
}>
// Component just renders its children if preview mode is not enabled
export function LiveQueryWrapper<T>(props: PreviewWrapperProps<T>) {
const {
// Is live query mode active?
isEnabled = false,
// If so, listen to this query
query = null,
// With these params
params = {},
// Separate remaining props to pass to the child
...rest
} = props
// Render child, with the wrapper's initial data and props
if (!isEnabled || !query) {
const nonPreviewProps = {...rest, data: props.initial.data}
return <Slot {...nonPreviewProps} />
}
// Swap initialData for live data
return (
<LiveQueryData<typeof props.initial.data> initial={props.initial} query={query} params={params}>
{props.children}
</LiveQueryData>
)
}
Create the LiveQueryData
component file:
// ./components/LiveQueryData.tsx
'use client'
import {Slot} from '@radix-ui/react-slot'
import {QueryParams} from '@sanity/client'
import {QueryResponseInitial, useQuery} from '@sanity/react-loader'
import {PropsWithChildren} from 'react'
type PreviewDataProps<T = any> = PropsWithChildren<{
initial: QueryResponseInitial<T>
query: string
params: QueryParams
}>
// Browser-only preview component
export function LiveQueryData<T = any>(props: PreviewDataProps<T>) {
const {initial, query, params = {}, ...rest} = props
const {data} = useQuery<T>(query, params, {initial})
const previewProps = {...rest, data}
return <Slot {...previewProps} />
}
Here is a simplified example of a Next.js route using these preview wrapper components.
Take note that:
loadQuery
function is checking if draftMode is enabled so that the initial data is either fetched using either the published
or previewDrafts
perspective. Without this, you may see an initial flash of published content before live preview updates to draft contentLiveQueryWrapper
and passed to the useQuery()
hookPresenterLayout
component does not have any props, because LiveQueryWrapper
will handle passing them down either from initial
, or live-updating content as a prop named data
LiveQueryWrapper
should expect to receive a data
prop with the published or draft Sanity content// ./app/presenter/[slug]/page.tsx
import {draftMode} from 'next/headers'
import {LiveQueryWrapper} from '@/components/LiveQueryWrapper'
import type {PresenterLayoutProps} from '@/components/PresenterLayout'
import PresenterLayout from '@/components/PresenterLayout'
import {loadQuery} from '@/sanity/lib/store'
import {PRESENTER_QUERY} from '@/sanity/queries'
export default async function Page({params}) {
const {isEnabled} = draftMode()
const initial = await loadQuery<PresenterLayoutProps['data']>(PRESENTER_QUERY, params, {
perspective: isEnabled ? 'previewDrafts' : 'published',
next: {tags: ['presenter']},
})
return (
<LiveQueryWrapper
isEnabled={isEnabled}
query={isEnabled ? PRESENTER_QUERY : ''}
params={isEnabled ? params : {}}
initial={initial}
>
<PresenterLayout />
</LiveQueryWrapper>
)
}
Here is a truncated example of what the PresenterLayout
component might look like, receiving the Sanity Content in the data
prop:
'use client'
type PresenterLayoutProps = {
data?: {
name?: string
title?: string
}
}
export default function PresenterLayout(props: PresenterLayoutProps) {
if (!props.data) {
console.log(`PresenterLayout data empty: ${JSON.stringify(props)}`)
return null
}
const {name, title} = props.data
return <div>{name}: {title}</div>
}
That's it! You should now be able to use your LiveQueryWrapper
component to wrap any component that expects to receive a data
prop – and whether you are in preview mode or production, see the expected layout with the right content.
Sanity Composable Content Cloud is the headless CMS that gives you (and your team) a content backend to drive websites and applications with modern tooling. It offers a real-time editing environment for content creators that’s easy to configure but designed to be customized with JavaScript and React when needed. With the hosted document store, you query content freely and easily integrate with any framework or data source to distribute and enrich content.
Sanity scales from weekend projects to enterprise needs and is used by companies like Puma, AT&T, Burger King, Tata, and Figma.
Sanity Studio is an incredibly flexible tool with near limitless customisation. Here's how I use it.
Go to An opinionated guide to Sanity StudioIt can be useful for testing plugins, front ends, or other integrations to have a Sanity Studio populated with fake content.
Go to How to generate massive amounts of demo content for SanitySetup "Live by Default" fetches and interactive live preview with Presentation in Sanity Studio
Go to Visual Editing with Next.js App Router and Sanity StudioSummarise form progression by decorating the entire editing form for a document with a component loaded at the root level.
Go to Create a document form progress component