Troubleshooting image rendering in Sanity.io, with guidance on resolving asset references and using @portabletext/react.

65 replies
Last updated: May 13, 2022
Hey all, I'm having a hard time making the images from my markdown/blockcontent render. I'm getting the following error (see screenshot). I'll attach snippets of my groq query and schema as well. Any guidance would be very helpful, I visited the Sanity "Block content image materializing" docs page but I'm still confused about how to integrate this properly.
Thanks in advance!
AI Update

I can help you with this! The issue you're running into is a common one when working with images in Portable Text/Block Content. Without seeing your screenshot, I'm guessing you're getting an error about missing image data or undefined asset references.

The core problem is that when you query Block Content with images, you need to explicitly dereference the image asset in your GROQ query. By default, image references in Block Content are just references (like {_type: 'image', asset: {_ref: 'image-abc123-...'}}) - they don't include the actual image URL or metadata.

The Solution: Use the -> Dereference Operator

In your GROQ query, you need to expand the asset reference using the dereference operator (->). Here's the pattern:

*[_type == "yourDocumentType"] {
  _id,
  title,
  body[]{
    ...,
    _type == "image" => {
      ...,
      asset->
    }
  }
}

The key part is asset-> which tells GROQ to fetch the full asset document instead of just the reference.

More Complete Example

If you're also using custom marks or annotations, you might need something like this:

*[_type == "post"] {
  _id,
  title,
  body[]{
    ...,
    _type == "image" => {
      ...,
      asset->{
        _id,
        url,
        metadata {
          lqip,
          dimensions
        }
      }
    },
    markDefs[]{
      ...,
      _type == "internalLink" => {
        "slug": @.reference->slug.current
      }
    }
  }
}

Schema Side

Make sure your schema includes the image type properly:

defineField({
  name: 'body',
  type: 'array',
  of: [
    {
      type: 'block'
    },
    {
      type: 'image',
      fields: [
        {
          name: 'alt',
          type: 'string',
          title: 'Alternative text'
        }
      ]
    }
  ]
})

When Rendering

When you render the Portable Text on the frontend, make sure you have a custom serializer for images. The exact implementation depends on your framework, but here's a general pattern:

// Example with @portabletext/react
import {PortableText} from '@portabletext/react'
import imageUrlBuilder from '@sanity/image-url'

const builder = imageUrlBuilder(client)

const components = {
  types: {
    image: ({value}) => (
      <img
        src={builder.image(value).width(800).url()}
        alt={value.alt || ''}
      />
    ),
  },
}

// Then use it
<PortableText value={body} components={components} />

The important thing is that once you've properly dereferenced the asset in your GROQ query with asset->, you'll have access to the full asset object with its URL and metadata, which you can then pass to Sanity's image URL builder or use directly.

Let me know if you're still stuck after trying the asset-> dereference - if you can share more details about the specific error message, I can help troubleshoot further!

What you’ve done looks good from what I can see. I’ve never encountered that error before, that’s odd.
For some reason it won't let me edit my original message with my schema, but yeah -- any ideas on what I can try here?
Here's the query
The "image" type at the very bottom appears just fine, but the image [7] in the "description" field doesn't render
The error in my console links to this article: https://www.sanity.io/help/block-content-image-materializing
Ah yes of course!
You need to resolve its reference.
description[] {
  ...,
  _type == "image" => {
    "image": asset->url
  )
}
Also, when I hit "copy url" here i'm able to visit the url
Oh I see, could you maybe help me understand what is happening in that block of code you sent?
and do you mean the query should be structured like so?
Edit: I'm missing a } at the end there
Right. So your
description
field is portable text. It’s an array of blocks. References are not automatically resolved in portable text, you have to do that yourself.
Hm, interesting
import groq from "groq";

export const eventDescriptionQuery = groq`
*[_type=="eventDescription"]{
    name,
    description,
    launchAt, 
    endAt,
    location,
    "id": _id,
    _type == "image" => {
    "image": asset->url
  }
  }`;
hm so with the above code, i'm still getting that same error unfortunately
So instead of just querying
description
like you did, we make that a bit more specific.
The brackets here mean we’re going to loop over its content.

description[] { <more stuff here> }
The dots here mean “query everything”, which is what your version does so far (still no references):

description[] {
  ...,
  <more stuff here>
}
The rocket (
=>
) is a condition. Here we say “do that bit when the block type is `image`”:
description[] {
  ...,
  _type == "image" => {
    <more stuff here>
  }
}
Finally, we resolve the asset reference to find the URL and assign it to an
image
property:
description[] {
  ...,
  _type == "image" => {
    "image": asset->url
  }
}
When you say "references", you mean the fields I enter into the schema, right? just want to make sure I understand
A reference is what happens when you link a different document or an image. When you add an image to your portable text, it creates a reference to an asset object. It doesn’t actually embed the image details right there.
I see! That makes sense
So you need to “resolve” that asset reference. This is what the arrow (
->
) does in a query. This is what you already do for your image field.
import groq from "groq";

`export
const eventDescriptionQuery = groq``
*[_type=="eventDescription"]{

name,

description[] {

...,

_type == "image" => {

"image": asset->url

}

}

launchAt,

endAt,

location,

"id": _id,

"image": image.asset->url,
`}`;`
so like this then?
But you also need to do the same for images that you’ve embedded in your portable text, in a similar fashion.
Yes, precisely. 🙂
Strange though, because i'm now getting the following:
Your groq query is invalid, you missed a comma.
omg lol oops, good catch!
err but i'm back to getting the original error now
I’m not sure how you render your portable text, but you’ll need to give a renderer to image blocks.
this is how I render the content
What is
BlockContent
?
it's how I render the markdown
to be completely honest I don't remember how or where I got the snippet from lol
but I import it as follows:

import BlockContent from "@sanity/block-content-to-react";
Alright.
This is the old library, and I believe it is deprecated. You should migrate to @portabletext/react .
The API is similar, and there is a guide for going from one lib to the other, so it should be relatively straightforward.
sweet, i'll look into this!
So one small question
do I install this into the Studio (sanity) or Web (src) side of things?
or globally?
Definitely on your frontend. This is part of your rendering layer, so it’s not related to the studio. 🙂
Hey so, I installed @portabletext/react, and i'm able to access my webpage with no errors, but the image still isn't appearing. I'm getting the following in my console:
Here's how I reference portabletext:
You’re not going to need
@sanity/image-url
, because we resolve the references manually in the query. It’s either one or the other.
With the query we authored together, your image component should receive the image URL as
value.image
. Maybe try that (or
console.log(value)
so we see what’s in therE.)
within my
sanityImageComponent
you mean?
Yes.
it worked!! I'm seeing the image now !! 🎉
Lovely. 💖
Although it's rendering in full size, if I wanted to add styling i'm assuming I should manipulate the style of &lt;img&gt; within SanityImageComponent?
Is there an "ideal" way to do this?
If you need the image dimensions, we can also query them as part of our query.
_type == "image" => {
  "image": asset->url,
  "dimensions": {
    "width": image.asset -> metadata.dimensions.width,
    "height": image.asset -> metadata.dimensions.height,
    "aspectRatio": image.asset -> metadata.dimensions.aspectRatio,
  }
}
(Considering you’re getting the dimensions to compute the aspect ratio, you could also query only the aspect ratio from Sanity, as shown above.)
Although it's rendering in full size, if I wanted to add styling i'm assuming I should manipulate the style of &lt;img&gt; within SanityImageComponent?
Yes, styling is up to you. Depends what you want to achieve. 🙂
Oh great 🙂 Thank you so much for your help!! You've been awesome
You could also add some options on the Sanity side, so when you add an image to the portable text, you can configure it with custom settings. You can then query these settings and use them to define styles.
How do you mean exactly? Do you have a docs reference for this by chance?
Right now you have this in your schema:
{
  type: "image",
  options: { hotspot: true }
}
You could do something like:

{
  type: "image",
  options: { hotspot: true },
  fields: [
    { title: 'Full width', name: 'isFullWidth', type: 'boolean', defaultValue: false }
  ]
}
Then you query that part:

_type == "image" => {
  isFullWidth,
  "image": asset->url,
  "dimensions": {
    "width": image.asset -> metadata.dimensions.width,
    "height": image.asset -> metadata.dimensions.height,
    "aspectRatio": image.asset -> metadata.dimensions.aspectRatio,
  }
}
And then in your component, you use
value.isFullWidth
to set specific styles or something.
This is just an example of course. You could have any sort of field to configure how your image gets rendered.
woah, that's so cool
it's really nice to see how flexible Sanity is coming off of using other CMS in the past
For real. 😊
thank you so much for your help :)
Of course. 💖

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?