Next.js fallback:true causes undefined post during prerender

9 replies
Last updated: Jan 28, 2026
I am not sure what I changed (not sure I even did) but I have these two queries
export const postQuery = groq`
  {
    'post': *[_type == "post" && slug.current == $slug] | order(_updatedAt desc)[0] {
      ${postFields}
    }
  }
`

export const postSlugsQuery = `
*[_type == "post" && defined(slug.current)][].slug.current
`
and then they are used in my
[slug].tsx
like so
export const getStaticPaths: GetStaticPaths = async () => {
  const posts = await sanityClient.fetch(postSlugsQuery)

  return {
    paths: posts.map((slug: string) => ({ params: { slug } })),
    fallback: true,
  }
}

export const getStaticProps: GetStaticProps = async ({ params }) => {
  if (!params) throw new Error('No path parameters found')

  const { slug } = params
  const { post } = await sanityClient.fetch(postQuery, { slug })

  if (!post) {
    return {
      notFound: true,
      revalidate: 10,
    }
  }

  return {
    props: {
      post,
    },
    revalidate: 3600,
  }
}
However, it is like a ghost post is showing up because I get

Error occurred prerendering page "/[slug]". Read more: <https://nextjs.org/docs/messages/prerender-error>
TypeError: Cannot read properties of undefined (reading 'mainImage')
    at Post (/Users/jamessingleton/Code/Projects/redshirt-sports/.next/server/pages/[slug].js:44:40)
    at Wc (/Users/jamessingleton/Code/Projects/redshirt-sports/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:68:44)
    at Zc (/Users/jamessingleton/Code/Projects/redshirt-sports/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:70:253)
    at Z (/Users/jamessingleton/Code/Projects/redshirt-sports/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:76:89)
    at $c (/Users/jamessingleton/Code/Projects/redshirt-sports/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:78:98)
    at bd (/Users/jamessingleton/Code/Projects/redshirt-sports/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:77:404)
    at Z (/Users/jamessingleton/Code/Projects/redshirt-sports/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:76:217)
    at $c (/Users/jamessingleton/Code/Projects/redshirt-sports/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:78:98)
    at bd (/Users/jamessingleton/Code/Projects/redshirt-sports/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:77:404)
    at Z (/Users/jamessingleton/Code/Projects/redshirt-sports/node_modules/react-dom/cjs/react-dom-server.browser.production.min.js:76:217)
Anyone have any ideas on troubleshooting this?
AI Update

This is a classic issue related to a recent breaking change in Sanity's API! The problem is that the default perspective changed from raw to published in API version 2025-02-19.

Here's what's happening: Your postSlugsQuery is likely returning slugs for posts that exist as drafts but haven't been published yet. When getStaticProps then tries to fetch those posts with postQuery, it's only getting published documents (the new default), so it returns undefined for draft-only posts. Since your query wraps the result in an object { 'post': ... }, you get { post: undefined }, which passes your if (!post) check but then fails when your component tries to access post.mainImage.

The Fix

You have a few options:

Option 1: Explicitly set perspective to raw (Quick fix)

Update your sanityClient configuration or queries to use perspective=raw:

// If using @sanity/client
const sanityClient = createClient({
  // ... your config
  perspective: 'raw', // Add this
  useCdn: false, // Important: CDN doesn't support raw perspective
})

Or set it per query:

const { post } = await sanityClient.fetch(postQuery, { slug }, { perspective: 'raw' })

Option 2: Use previewDrafts perspective (Better for preview scenarios)

If you want to see drafts overlaid on published content:

perspective: 'previewDrafts'

Option 3: Filter for published documents only (Production-ready)

Update your postSlugsQuery to only return published posts:

export const postSlugsQuery = groq`
*[_type == "post" && defined(slug.current) && !(_id in path("drafts.**"))][].slug.current
`

Additional Fixes

Your postQuery has an unnecessary object wrapper that's causing the undefined check to not work properly. Simplify it:

export const postQuery = groq`
  *[_type == "post" && slug.current == $slug] | order(_updatedAt desc)[0] {
    ${postFields}
  }
`

Then update your fetch:

const post = await sanityClient.fetch(postQuery, { slug })

This way, post will actually be null when not found, and your if (!post) check will work correctly.

Why This Happened

According to the API version 2025-02-19 documentation, Sanity changed the default perspective to published to support Content Releases. If you didn't explicitly set an API version, your client may have automatically upgraded to this version, causing draft documents to suddenly disappear from your queries.

The "ghost post" you're seeing is likely a draft document that shows up in your paths but doesn't exist in the published dataset!

Show original thread
9 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.

Was this answer helpful?