Image Uploaded to WYSIWYG - Link Provided on the Sanity CDN Doesn't Work

26 replies
Last updated: Jul 19, 2022
Heyo!
We have out blog on a react website using Sanity (and it all works very nicely) but when we include an image in the WYSIWYG the image link provided on the Sanity CDN doesn't work. Has anyone experienced this before?
AI Update

Glad to hear your blog is working nicely! This is a common issue when working with images in Portable Text. The problem is that Sanity stores image references as asset objects, not ready-to-use URLs. You need to transform these references into proper CDN URLs using @sanity/image-url.

Here's what's happening: when you add an image in the Portable Text editor (WYSIWYG), Sanity stores it as a reference object with an _type of image and an asset reference. But to actually display it, you need to process that reference through Sanity's image URL builder to get a working cdn.sanity.io URL.

The solution is to create a custom serializer for image blocks in your Portable Text renderer using @portabletext/react:

import { PortableText } from '@portabletext/react'
import imageUrlBuilder from '@sanity/image-url'
import { client } from './sanityClient' // your Sanity client

// Set up the image URL builder
const builder = imageUrlBuilder(client)

function urlFor(source) {
  return builder.image(source)
}

// Custom serializers for Portable Text
const components = {
  types: {
    image: ({value}) => {
      if (!value?.asset?._ref) {
        return null
      }
      return (
        <img
          src={urlFor(value).width(800).url()}
          alt={value.alt || ' '}
          loading="lazy"
        />
      )
    }
  }
}

// Use it in your component
function BlogPost({ content }) {
  return <PortableText value={content} components={components} />
}

Key steps:

  1. Install @sanity/image-url if you haven't: npm install @sanity/image-url
  2. Create an image URL builder instance with your Sanity client
  3. Add a custom serializer for the image type in your Portable Text components
  4. Use the builder to transform the asset reference into a working cdn.sanity.io URL

You can also add image transformations like .width(), .height(), .fit(), .quality(), or even .auto('format') for automatic WebP/AVIF format selection to optimize the images for your needs.

If your images are structured differently (like custom image blocks), you might need to adjust the value.asset._ref path to match your schema. The important part is passing the image reference through the URL builder before rendering it.

What does “doesn’t work” mean in that context?
Sorry, that was vague!
I mean it provides a link to an image for the img tag to load, but when you navigate to/try to load that image it says not found.
Its as if the image isn't hosted at all
What does the link look like?
Sorry for taking so long!

https://cdn.sanity.io/images/undefined/undefined/b75288070171082c939875ddf983fac43c42dce4-800x800.jpg?w=800&amp;fit=max&amp;auto=format

So I've been digging in all afternoon and my theory is its sanity url builder issue, because its returning undefined/undefined as above
Right, right. Where did you get that URL please? 🙂
From the image that fails to load on the post itself.
So I've tried a few things this afternoon, and hoping one of them can help tell you what my issue is haha!


So when I don't use urlBuilder from sanity/image-url it shows as error saying I need to import the image component into the PortableText component as a prop.

So I create an image component, and use the url builder example from the sanity website and it give undefined/undefined


This is where I am copying from

https://www.sanity.io/docs/portable-text-to-react#available-components
Can you show me both your query and the JSX for your image please?
Image component:

import { PortableText } from "@portabletext/react";
import urlBuilder from "@sanity/image-url";
import { getImageDimensions } from "@sanity/asset-utils";

// Barebones lazy-loaded image component
const BlogImage = ({ value, isInline }) => {
  const { width, height } = getImageDimensions(value);
  console.log(urlBuilder());
  return (
    <>
      {/* eslint-disable-next-line @next/next/no-img-element */}
      <img
        src={urlBuilder()
          .image(value)
          .width(isInline ? 100 : 800)
          .fit("max")
          .auto("format")
          .url()}
        alt={value.alt || " "}
        loading="lazy"
        style={{
          // Display alongside text if image appears inside a block text span
          display: isInline ? "inline-block" : "block",

          //   // Avoid jumping around with aspect-ratio CSS property
          //   aspectRatio: width / height,
        }}
      />
    </>
  );
};

export default BlogImage;
PortableText component usage:

<PortableText
              value={post.body}
              components={{
                types: {
                  image: BlogImage,
                },
              }}
            />
Ah.
I got it.
You don’t pass your project/dataset to the URL builder.
Which is why you get undefined/undefined.
Oh my word
lets try this
thank you!
😊
Sorry... I know this is probably obvious but I'm going stir crazy, where do I find the details for my sanity config?
In your sanity.json typically?
Yeah I was being epically dumb, the heat is getting to me
I found it as you messaged lol
You're a hero Kitty, got it working
thank you!
Awww, glad to help!
Very instructive thread!

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?