Handling empty content in Sanity previews with fallbacks

12 replies
Last updated: Sep 17, 2021
Does anyone have a solution for how to handle previews breaking when content is empty?
For example, I would like to have a fall-back for all my modules so that if one is empty, it doesn't break the preview (maybe displays a message telling the editor to choose an image or insert content, etc).
AI Update

This is a common challenge! Here are several approaches you can use to handle empty content gracefully in your previews:

1. Custom Preview with Fallbacks in Schema

The most straightforward approach is using the preview configuration in your schema with a prepare function that handles empty states:

export default {
  name: 'myModule',
  type: 'object',
  fields: [
    {name: 'title', type: 'string'},
    {name: 'image', type: 'image'},
    {name: 'content', type: 'text'}
  ],
  preview: {
    select: {
      title: 'title',
      image: 'image',
      content: 'content'
    },
    prepare({title, image, content}) {
      return {
        title: title || '⚠️ No title added',
        subtitle: content || 'Click to add content',
        media: image || () => '📝' // or import an icon from @sanity/icons
      }
    }
  }
}

You can even provide more helpful feedback:

prepare({title, image, content}) {
  const missing = []
  if (!title) missing.push('title')
  if (!image) missing.push('image')
  if (!content) missing.push('content')
  
  return {
    title: title || 'Incomplete module',
    subtitle: missing.length ? `Missing: ${missing.join(', ')}` : 'Ready',
    media: image
  }
}

2. Add Validation Rules

Prevent empty content from being saved in the first place using validation rules:

{
  name: 'image',
  type: 'image',
  validation: Rule => Rule.required().error('Please add an image')
},
{
  name: 'content',
  type: 'text',
  validation: Rule => Rule.required().warning('Content is recommended')
}

3. Defensive Coding in Your Frontend Preview

Always check for empty values in your actual preview/frontend code:

function ModuleComponent({ module }) {
  if (!module?.image || !module?.content) {
    return (
      <div className="preview-placeholder">
        <p>⚠️ This module needs content</p>
        <ul>
          {!module?.image && <li>Add an image</li>}
          {!module?.content && <li>Add content</li>}
        </ul>
      </div>
    )
  }
  
  return (
    // your normal component
  )
}

Or use fallback values with optional chaining:

const imageUrl = module?.image?.asset?.url ?? '/placeholder-image.jpg'
const title = module?.title ?? 'Untitled'
const content = module?.content ?? 'No content yet'

4. Custom Input Components

For more complex scenarios, you can create a custom input component that shows warnings directly in the editing interface:

const CustomModuleInput = (props) => {
  const {title, image, content} = props.value || {}
  const hasEmptyFields = !title || !image || !content
  
  return (
    <Stack space={3}>
      {hasEmptyFields && (
        <Card tone="caution" padding={3}>
          <Text>⚠️ Please complete all fields for this module</Text>
        </Card>
      )}
      {props.renderDefault(props)}
    </Stack>
  )
}

// In your schema:
{
  name: 'myModule',
  type: 'object',
  components: {
    input: CustomModuleInput
  }
}

The combination of preview configuration with fallbacks (for Studio UI) and defensive coding in your frontend (for actual preview rendering) usually works best. This way editors get clear feedback about what's missing, and your preview never breaks even if incomplete content slips through.

If you’re looking for a fallback if any item in an array is missing a field, that’s also possible. Please let me know if that’s what you’re after.
Thank you - I am reviewing with my team. I will let you know 🙂
Thank you - I am reviewing with my team. I will let you know 🙂
user A
what about something like this for an image:

preview: {
    select: {
      image: "image.secure_url",
      sizing: "layoutSettings.sizing",
    },
    prepare({ image, sizing }) {
      return {
        title: `Image ${sizing}`,
        media: <img src={image.replace(/upload\/v\d+\//, "upload/w_40/")} />,
      };
    },
  },

media
might look like:

import React from 'react'

// ...

media: image ? <img src={image.replace(/upload\/v\d+\//, "upload/w_40/")} /> : <>🤷‍♀️</>,
If you want
title
to render a certain message based on there being no image, you can do that in a conditional too. With more extensive logic, you might do this outside of the return.
media
might look like:

import React from 'react'

// ...

media: image ? <img src={image.replace(/upload\/v\d+\//, "upload/w_40/")} /> : <>🤷‍♀️</>,
If you want
title
to render a certain message based on there being no image, you can do that in a conditional too. With more extensive logic, you might do this outside of the return.
Ah, I see this affects the small preview in the editor, but what about in the web preview tab?
That would be handled by the front end code that the live preview is using. Basically the same approach – see if an image exists and return accordingly.
If I remember right you’re not using React, but if you were, it might be something like (where
is your image handling):

{image && <img src=… />}

Or a conditional like:


{image ? <img src=… /> : <img src='fallback.png' />}
With live preview and Sanity you’ll want to optionally chain almost everything.

{image?.asset?._ref && <img…

Assume nothing exists, and that every object is empty!
Thank you for your help! I was finally able to get my preview from breaking, and get to end my day with a win. Have a great weekend!
Great news! Enjoy yours as well!

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?