<SRCImage /> vs <Image /> render problems

We are migrating some of our RSC components from <Image /> to <SRCImage />. I have some rendering issues on with certain images:

Render with <Image />

Render with <SRCImage />

(see SpaceX, Vattenfall, JD, MB, and some other logos)

Whay am I missing here?

Additinal question for explanation. I am not even sure the exact difference between both components. I have read this but not sure what I gain with SRCImage… Can someone explain does it make even sense to migrate from <Image />

1 Like

@primoz.rome,

This might be an alpha channel issue? If you can please link to the specific images in question, I can take a look in a graphics editor for you.

e.g. (from the readme):

The placeholder is set as the background to the image itself. Be careful: the placeholder is not removed when the image loads, so it’s not recommended to use this component if you anticipate that the image may have an alpha channel with transparencies.

===========

<SRCImage/> is a further, optional optimization for devs who want to use React Server Components to minimize clientside JS. But as you see, it doesn’t work perfectly in all cases…

We have a few options here:

  • Easiest and my recommendation: Just keep using <Image/> if it’s not causing any issues or SEO/performance penalties for you. Leave the over-optimizations for the purists :slight_smile:

  • Relatively clean once made, but requires upfront changes and testing: You can roll your own image solution instead of using ours, e.g. using Next/Image or your own HTML srcset attributes and picture tags.

  • Time-consuming and imperfect results: We can look at the specific images in question that are having issues, and see if it’s indeed the alpha channel causing issues. If so, we can modify those specific images to not use alpha transparency, instead either dithering them to white or black or else just removing the alpha channel (which will cause some aliasing around circle and curve edges).

  • If we can establish a pattern (test suite & reproducible examples) of consistently problematic image types, I can submit it as a bug report and see if we might be able to modify SRCImage behavior on our side — but we’d have to be very careful with that to not break existing images for you and other users. This is probably a last resort.

PS I just saw your other thread (Different behavior while fetching and displaying images with react-datocms <Image /> component - #3 by primoz.rome) with more background context.

If this is part of a big upgrade project and you’re running into issues with both <Image> and <SRCImage/>, maybe you can share your current codebase with us and tell us what issues you’re facing with them, altogether, and we can try to figure out a more holistic solution with you? (If simply switching back to <Image/> isn’t good enough)

Would that help? If so, please feel free to send us all the details at support@datocms.com and we can take a look at the codebase and the actual example images together?

Hello @roger . What we figured is that the latest version of <Image /> package gives us strange rendering results - something not happening with older version of the package we used up to now.

Just as an example result, here is our <ImageBlock /> component that renders Image blocks we are adding to Structured Text fields.

import { ImageBlockRecord } from "@/data/graphql/generated";
import { Box } from "@chakra-ui/react";
import { SRCImage, Image } from "react-datocms";

interface ImageBlockProps {
  block: ImageBlockRecord;
}

/**
 * Renders an image block from StructuredText
 */
export function ImageBlock({ block }: ImageBlockProps) {
  if (!block.image?.responsiveImage) {
    return null;
  }

  const defaultProps = {
    display: "flex",
    flexDirection: "column",
    alignItems: "center",
  };

  // Merge defaultProps and block.styleProps (with styleProps taking priority)
  const mergedProps = {
    ...defaultProps,
    ...(block?.styleProps ?? {}),
  };

  return (
    <Box as="figure" {...mergedProps}>
      <Box className="image" rounded="xl" overflow="hidden" shadow="md">
        <SRCImage data={block.image.responsiveImage} />
      </Box>

      {block.showCaption && (
        <Box as="figcaption" fontStyle="italic" textAlign="center">
          {block.caption ? block.caption : block.image.responsiveImage.alt}
        </Box>
      )}
    </Box>
  );
}

So this component always used <Image />. With previous packages images were rendered okay, but now images are streched for some reason. Here is the result using <Image />:

If I switch <Image /> to <SRCImage /> images are rendered correctly:

In both above cases DatoCMS GraphQL API delivers the same image: https://www.datocms-assets.com/53444/1691065189-obsidian-r8r-with-dewesoft-mobile-app.jpg?ar64=MTY6OQ&auto=format&dpr=2&fit=crop&w=400

By using <Image /> i have this strange vertical image scalling issue everywhere.

It’s strange because we used <Image /> for years, but with older version of the package…

Was it a major version difference? We might’ve changed the output between major versions… if it was a minor or patch version, I’d consider that a bug and can look more into it if you can narrow down the version range where it might’ve happened…?

But I don’t think it’s worth worrying about as long as you can get it working right in the new version.

It looks like you’re using both Chakra and a <figure/> tag, both of which will likely interact with your frontend CSS and our custom element’s rendering, maybe making it a bit unpredictable. If I were you’d I’d probably stick with one approach or the other (i.e., just provide the srcSet URLs to Chakra directly and let it handle the image rendering without our custom component, OR rely on our component as much as possible and only wrap it with the minimal divs and CSS that you absolutely need to).

If you do need to mix and match them more precisely than that (i.e., <figure> + <Box> + our custom component), we’d probably have to debug the frontend and inspect those elements so we can trace the CSS that’s causing the warping. Then either you can apply an override in your CSS or, if it’s a bug on our side, we can fix it.

What would be best for you? If you have a public preview available, I can take a look there, or you can check on your own, or we can jump on a call, or you can share the frontend source with me and I can take a look…? Or if you already got it working with <SrcImage/>, that’s fine too and we don’t have to take any more action…? Up to you!

Hey @roger

Was it a major version difference? We might’ve changed the output between major versions… if it was a minor or patch version, I’d consider that a bug and can look more into it if you can narrow down the version range where it might’ve happened…?

We used to use: "react-datocms": "^4.0.8",

On the new project for upgrade to NextJS 15 we use "react-datocms": "^7.2.2",

It looks like you’re using both Chakra and a <figure/> tag, both of which will likely interact with your frontend CSS and our custom element’s rendering, maybe making it a bit unpredictable.

That is correct but if I just change <Image /> to <SRCImage /> in our image component the <Image /> will strech image vertically, <SRCImage /> will render it correctly:

<Box as="figure" {...mergedProps}>
  <Box className="image" rounded="xl" overflow="hidden" shadow="md">
     <Image data={block.image.responsiveImage} />
  </Box>

  {block.showCaption && (
    <Box as="figcaption" fontStyle="italic" m={0} p={2} textAlign="center">
      {block.caption ? block.caption : block.image.responsiveImage.alt}
    </Box>
  )}
</Box>

to this:

<Box as="figure" {...mergedProps}>
  <Box className="image" rounded="xl" overflow="hidden" shadow="md">
      <SRCImage data={block.image.responsiveImage} />
  </Box>

  {block.showCaption && (
    <Box as="figcaption" fontStyle="italic" m={0} p={2} textAlign="center">
      {block.caption ? block.caption : block.image.responsiveImage.alt}
     </Box>
   )}
</Box>

I checked CSS for my <figure> and Chakra’s <Box> (eventually renders <div>) and I have width and height properties on both elements set to auto, so it shouldn’t really mess with how the image is rendered.

Looking at rendered source <Image /> will render 700x252 px, but source image is 700 x 191 px.

With <SRCImage /> the rendered image is correctly 700x191

Confused :thinking:

Is there a preview instance I can see, or could you perhaps share the repo with me (if you want a deeper investigation)?

It’s a pretty complex setup, and I’d need to look at the whole stack to (try to) better understand every part that’s interacting with it.

Another simple test: Does anything change if you change both <figure> and <Box> to plain <div>s or even <Fragment>s, with no classes or styling at all?

Also, to be clear… if <SRCImage/> is working fine for you, you can just use that. 3 major version jumps is quite a lot, and I would definitely expect breaking changes between them. We can keep investigating the <Image> situation if it’s helpful, but otherwise we can skip the deeper dive. What do you think?

Is there a preview instance I can see, or could you perhaps share the repo with me (if you want a deeper investigation)?

Yes we could work this out… But I need some time to setup preview for you. I can also give you direct access to GitHub repo…

Another simple test: Does anything change if you change both <figure> and <Box> to plain <div>s or even <Fragment>s, with no classes or styling at all?

I have completely removed both <figure> and <Box> to have only this:

/**
 * Renders an image block from StructuredText
 */
import { SRCImage, Image } from "react-datocms";

export function ImageBlock({ block }: ImageBlockProps) {
  return <Image data={block.image.responsiveImage} />;
}

but the result is the same… <Image /> is vertically streched while <SRCImage /> is okay. I can use <SRCImage /> but it really gives me headaches that I can not use <Image />. I have some problems with SRCImage (CLS for example)…

Would that be all right? You can share it with r.tuan@datocms.com / https://github.com/arcataroger/

I can fork it and try it out locally.

Hello @roger added to the GitHub project, I have shared you example env file to the private message

Hey @primoz.rome,

This is due to lines 69 and 70 in your components/ui/prose.tsx, which adds:

    "& img": {
        marginTop: "1.7em",
        marginBottom: "1.7em",
    },

If you comment those out, <Image> should look OK again:

If you need to use those rules for some other pictures, could you maybe namespace them (with CSS modules) and/or apply them only to certain classes, or exclude ours from them?

1 Like

Uff, I saw that rule but I have automatically though top/bottom margin should not effect to image stretching… Didn’t try thought. I will work with this, and again thank you. I should figure this one myslef …

1 Like

No worries, the ā€œcascadingā€ part of CSS is always confusing, especially when working with multiple styling systems & third-party components! Hopefully that was the issue, but let us know if you need more help!

Hey @roger, do you maybe know why <SRCImage /> would leave the blured base64 encoded image?

Below is the actual image is, but the base64 encoded blur stays and is visible trough the image if it is transparent…

Sorry @primoz.rome, that is by design, as per the above post:

It is one of the quirks of using <SRCImage/> instead of <Image/>, I’m afraid. It has no clientside JS and so it can’t unload itself. As far as I know (but please let me know if this is wrong), there isn’t a way to specify a ā€œrealā€ placeholder for an image, so we just have to fake it by making that the image background until the real one loads:

But one of the downsides is that it cannot remove the placeholder once the real one does load. You need JS for that, which is what the normal <Image/> component does.

If you need more control than that, you can write your own <picture/> and srcset code, but as far as I know, there isn’t a JS-free way to make an image placeholder that can properly account for transparency… though please let me know if I’m wrong about that!

(Edit: Deleted previous workaround because it didn’t work, sorry… originally I explored using a CSS mask to obscure the parts of the placeholder that are outside of the real image boundaries, but that required additional fetch calls just to get the mask… not worth it.)

I did find an imperfect workaround… you can use a CSS animation to hide the background after a fixed number of seconds:

e.g.:

              style={{
                backgroundImage: `url("${responsiveImage.base64}")`,
                backgroundSize: "cover",
                backgroundRepeat: "no-repeat",
                backgroundPosition: "50% 50%",
                color: "transparent",
                animation: "fade 3s forwards"
              }}

Where the fade animation is simply:

@keyframes fade {
  to  { background: unset; }
}

That will remove that placeholder background after 3 seconds… regardless of whether the actual image ever loaded. Somewhere between 3-5 secs is probably a good amount of time, but you can never really be sure how fast the user’s connection is. You could detect it with Javascript, but then if you’re doing that, you might as well just use JS to unload the placeholder once the real one loads…

2 Likes

Also, from a more practical perspective, might I suggest that you don’t necessarily have to use a one-size-fits-all approach to all your images? It could be a case-by-case thing, where some images:

  • Might be better as an SVG (like your circuit diagram)
  • Might not need a placeholder at all (the image itself is only 4 KB to begin with)
  • Might not need an alpha channel… if your background is always white and you don’t have a dark mode, you can remove the alpha altogether
  • Using the regular <Image/> component on an as-needed basis

Thanks for digging into this. And yes, I am approaching case-by-case to Images, just wanted to understand whats going on under the hood so I can better decide whats optimal to use where! Thanks again @roger

2 Likes