Build Error: Cannot read properties of null (reading 'mainImage') in Sanity
Based on your error, it looks like you're trying to access mainImage on a post object that is null during the build process. This is a common issue with Next.js static site generation when some of your posts don't have a mainImage field populated, or when a post document doesn't exist at all.
Here are the most common causes and solutions:
1. Handle null/undefined posts
If the post itself doesn't exist (maybe you're generating paths for posts that were deleted), you need to check if the post exists before accessing its properties:
// In your page.tsx
const post = await getPost(slug);
if (!post) {
notFound(); // Next.js 13+ App Router
// or return null / redirect
}
// Now safely access post.mainImage
const imageUrl = post.mainImage ? urlFor(post.mainImage) : null;2. Handle optional mainImage in your GROQ query
Use the coalesce() function in your GROQ query to provide fallback values:
*[_type == "post" && slug.current == $slug][0] {
_id,
title,
slug,
"mainImage": coalesce(mainImage, null),
// or provide a default image object
body,
author->
}3. Add optional chaining in your component
On line 53 of your page.tsx, use optional chaining to safely access mainImage:
// Instead of:
const imageUrl = urlFor(post.mainImage).url();
// Use:
const imageUrl = post.mainImage ? urlFor(post.mainImage).url() : '/default-image.jpg';
// Or with optional chaining:
const imageUrl = post?.mainImage ? urlFor(post.mainImage).url() : null;4. Make mainImage optional in your schema
In your post.ts schema, ensure mainImage is marked as optional (which it likely already is):
{
name: 'mainImage',
title: 'Main Image',
type: 'image',
// No 'validation: Rule => Rule.required()' means it's optional
options: {
hotspot: true,
},
}5. Filter out posts without mainImage during build
If you're using generateStaticParams, you can filter out posts that don't have the required fields:
export async function generateStaticParams() {
const posts = await client.fetch(`
*[_type == "post" && defined(mainImage)] {
"slug": slug.current
}
`);
return posts.map((post) => ({
slug: post.slug,
}));
}The defined() function checks if the field exists and isn't null.
Most likely solution
Based on typical Next.js + Sanity setups, you probably need to add a null check before accessing mainImage on line 53. The build process is trying to generate static pages for all posts, and at least one post either doesn't exist or doesn't have a mainImage field populated.
Try wrapping your image rendering logic like this:
{post.mainImage && (
<Image
src={urlFor(post.mainImage).url()}
alt={post.title}
// ... other props
/>
)}This will prevent the error by only rendering the image when mainImage actually exists. You can learn more about null handling in GROQ queries to handle these situations at the query level rather than in your component code.
Show original thread7 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.