Next.js fallback:true causes undefined post during prerender

9 replies
Last updated: Jun 28, 2022
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
So putting a
posts.map((post: Post) => console.log(post.slug))
in the
getStaticPaths
returns a lot of undefined šŸ¤”
I think the main problem is probably in your render. You’re trying to read
.mainImage
somewhere.
Yes but my posts have a mainImage
I’d put a console.log before returning the
post
in
getStaticProps
. You probably have an incomplete post. Maybe a draft?
So that’s why I'm confused. It says it generated 54/54 pages but errors out šŸ˜”
Nope no draft posts
If I comment it mainImage it just goes to the next item I'm reading from post
user F
I figured it out 🤦 for some reason if I have
fallback: true
it fails but having
fallback: 'blocking'
enables it to build
Ah yes, that makes a lot of sense.
fallback: true
means ā€œrender the component without props until we get them from the clientā€œ, hence why it fails on reading post properties.

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?