July 06, 2023

Create a reusable live preview component

By Simeon Griggs

When setting up live preview, 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 live preview for the first time, start with one of these guides:

The guides above walk through how to set up these client libraries:


  • You already have a Sanity Studio with draft and published content
  • You’re working on a React project with live preview with @sanity/preview-kit already configured


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.


preview-kit now exports a <LiveQuery> wrapper component which does the same thing as shown in this guide. Use it, or continue on if you'd prefer to do it yourself!

How this will work

  1. First, you’ll create a component named PreviewWrapper which will take the initial data you initially fetch server-side.
  2. You’ll wrap that component around a single component designed to consume and render that initial data.
  3. The PreviewWrapper component will prop-drill that initial data unless preview mode is active and a query is supplied.
  4. In that case, it will wrap the child component in an additional client component which performs the live query and prop-drills draft content as it updates.

Why this works

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.

Working example

The "Course Platform Demo" is a Sanity Studio and Next.js App router application that uses this method to reuse the PreviewWrapper component in every route to make each unique layout automatically update 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.

Creating the preview components

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 PreviewWrapper, which will wrap the component you use to render Sanity content:

// PreviewWrapper.tsx

import {Slot} from '@radix-ui/react-slot'
import {QueryParams} from '@sanity/client'
import {PropsWithChildren} from 'react'

import {PreviewData} from '@/components/PreviewData'

type PreviewWrapperProps<T> = PropsWithChildren<{
  initialData: T
  preview?: boolean
  query?: string
  params?: QueryParams

// Component just renders its children if preview mode is not enabled
export function PreviewWrapper<T>(props: PreviewWrapperProps<T>) {
  const {
    // Is preview mode active?
    preview = false,
    // If so, listen to this query
    query = null,
    // With these params
    params = {},
    // Separate remaining props to pass to the child
  } = props

  // Render child, with the wrapper's initial data and props
  if (!preview || !query) {
    const nonPreviewProps = {...rest, data: props.initialData}

    return <Slot {...nonPreviewProps} />

  // Swap initialData for live data
  return (
    <PreviewData<typeof props.initialData>

Create the PreviewData component file:

// PreviewData.tsx

'use client'

import {Slot} from '@radix-ui/react-slot'
import {QueryParams} from '@sanity/client'
import {useLiveQuery} from '@sanity/preview-kit'
import {PropsWithChildren} from 'react'

type PreviewDataProps<T = any> = PropsWithChildren<{
  initialData: T
  query: string
  params: QueryParams

// Browser-only preview component
export function PreviewData<T = any>(props: PreviewDataProps<T>) {
  const {initialData, query, params = {}, ...rest} = props
  const [data] = useLiveQuery(initialData, query, params)

  const previewProps = {...rest, data}

  return <Slot {...previewProps} />

Using the preview components

Here is an example of a Next.js route using these preview wrapper components.

Take note that:

  • A getClient function is returning a specific version of Sanity client with the correct perspective configured so that the initial data is either published or previewDrafts – without this step, you may see an initial flash of published content before live preview updates to draft content
  • The same query and params used for the initial fetch are prop-drilled to PreviewWrapper and passed to the useLiveQuery() hook
  • The child PresenterLayout component does not have any props, because PreviewWrapper will handle passing them down either from initialData or live updating content as a prop named data
  • Therefore, any child of PreviewWrapper 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 {presenterQuery} from '@/sanity/queries'
import {getClient} from '@/sanity/client'

export default async function PresenterPage({params}) {
  const {isEnabled: preview} = draftMode()
  const data = await getClient(preview).fetch(presenterQuery, params)

  return (
      <PresenterLayout />

Here is a truncated example of what the PresenterLayout component might look like, receiving the Sanity Content in the data prop:

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 PreviewWrapper 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 – build remarkable experiences at scale

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.

Other guides by author

Create richer array item previews

Object types use a preview property to display contextual information about an item when they are inside of an array; customizing the preview component can make them even more useful for content creators.

Simeon Griggs
Go to Create richer array item previews