PortableText "Objects are not valid as a React child" error in Next.js

5 replies
Last updated: Jan 28, 2026
Hey Team! I am trying to implement Portable Text into my Sanity/Next project. I am using the template template-nextjs-personal-website which uses
_import_ {
PortableText,

_type_ PortableTextBlock,

_type_ PortableTextComponents,
}
_from_ 'next-sanity'

I am stuck with this error and have been triple checking everything, has anyone ran into this?

Error: Objects are not valid as a React child (found: object with keys {markDefs, children, _type, style, _key}). If you meant to render a collection of children, use an array instead.



        defineField({
          name: 'optionalText',
          title: 'Optional Text',
          type: 'array',
          of: [
            defineArrayMember({
              lists: [],
              marks: {
                annotations: [
                  {
                    name: 'link',
                    type: 'object',
                    title: 'Link',
                    fields: [
                      {
                        name: 'href',
                        type: 'url',
                        title: 'Url',
                      },
                    ],
                  },
                ],
                decorators: [
                  {
                    title: 'Italic',
                    value: 'em',
                  },
                  {
                    title: 'Strong',
                    value: 'strong',
                  },
                ],
              },
              styles: [],
              type: 'block',
            }),
          ],
          validation: (rule) => rule.max(155).required(),
        }),
Trying to render

<CustomPortableText
   paragraphClasses="font-serif max-w-3xl text-gray-600 text-xl"
   value={hero.optionalText}
/>
AI Update

I've seen this exact error before! The issue is that your optionalText field is being returned from Sanity as an array of Portable Text blocks, but somewhere in your code you're likely trying to render the individual block objects directly instead of passing the array to the PortableText component.

The error message Objects are not valid as a React child (found: object with keys {markDefs, children, _type, style, _key}) is telling you that React is receiving a raw Portable Text block object (with those internal keys) instead of properly rendered content.

Here are the most common causes and fixes:

1. Check your GROQ query

Make sure you're querying optionalText correctly. It should return the full array:

// âś… Correct
const query = `*[_type == "yourType"][0]{
  optionalText
}`

// ❌ Incorrect - don't access array elements or nested properties
const query = `*[_type == "yourType"][0]{
  optionalText[0]
}`

2. Verify the data structure

Before passing to CustomPortableText, log the value to confirm it's an array:

console.log('optionalText:', hero.optionalText)
// Should be: [{_type: 'block', ...}, {_type: 'block', ...}]
// Not: {_type: 'block', markDefs: [], ...}

3. Check for conditional rendering issues

If you're conditionally rendering or mapping over data, make sure you're not accidentally passing a single block object:

// ❌ Wrong - mapping creates individual blocks
{hero.optionalText?.map(block => (
  <CustomPortableText value={block} />
))}

// âś… Correct - pass the whole array
<CustomPortableText value={hero.optionalText} />

4. Ensure CustomPortableText is set up correctly

Your CustomPortableText component should be using the PortableText component from next-sanity properly. Based on the @portabletext/react documentation, it should look something like:

import { PortableText, type PortableTextComponents } from 'next-sanity'

export function CustomPortableText({ 
  value, 
  paragraphClasses 
}: { 
  value: any
  paragraphClasses?: string 
}) {
  const components: PortableTextComponents = {
    block: {
      normal: ({ children }) => (
        <p className={paragraphClasses}>{children}</p>
      ),
    },
  }

  return <PortableText value={value} components={components} />
}

5. Check for Next.js 15 async params

If you're using Next.js 15, make sure you're awaiting params if you're in a page component:

// Next.js 15
export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params
  // ... fetch data
}

The most likely culprit is either your GROQ query returning a single block instead of an array, or somewhere in your component tree you're accidentally passing hero.optionalText[0] or accessing a nested property that returns a single block object.

If you're still stuck, share your GROQ query and the output of console.log(typeof hero.optionalText, hero.optionalText) right before the <CustomPortableText> component, and we can narrow it down further!

Show original thread
5 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?