lorenzodejong / next-sanity-image

Utility for using responsive images hosted on the Sanity.io CDN with the Next.js image component.
MIT License
149 stars 20 forks source link

Image with src has a "loader" property that does not implement width. #20

Closed rafaelderolez closed 2 years ago

rafaelderolez commented 3 years ago

Getting the following error when using next-sanity-image with "next": "^11.1.0",

Code:

const imgOpts = (transform) => transform.width(1920).height(1080).fit('crop');

export default function Component() {
  const imageProps = useImage(data.backgroundImage, imgOpts);
  return (
    <Img
      {...imageProps}
    />
  )
}

Console:

instrument.js?ea14:109 Image with src "[sanityUrl].jpg?rect=0,137,2640,1485&w=1920&h=1080&q=80&fit=crop&auto=format&dpr=2" has a "loader" property that does not implement width. Please implement it or use the "unoptimized" property instead.
Read more: https://nextjs.org/docs/messages/next-image-missing-loader-width 
    at Image (webpack-internal:///./node_modules/next/dist/client/image.js:307:20)

The error also links to the following page: https://nextjs.org/docs/messages/next-image-missing-loader-width This warning was recently added in https://github.com/vercel/next.js/pull/26998

lorenzodejong commented 3 years ago

Hey @rafaelderolez, thanks for opening this issue. The reason why this warning is being shown by the next/image component is because you're not using the injected "width" property for the current srcSet entry in the image.

If you replace width(1920) with width(options.width) the warning will be omitted. However from your example you would also want to crop the image while retaining the aspect ratio.

const cropRatio = 1080 / 1920;

const imgOpts = (transform, options) => {
    return transform.width(options.width).height(options.width * cropRatio).fit('crop');
};

export default function Component() {
    const imageProps = useNextSanityImage(sanityCDNClient, image, { imageBuilder: imgOpts });
}

This behavior is also mentioned in the documentation in the "gotcha's" (https://github.com/bundlesandbatches/next-sanity-image#gotchas).

rafaelderolez commented 3 years ago

You're absolutely right, we were using it wrong! Thanks for clarifying ✌️

lorenzodejong commented 3 years ago

No worries, happy to help :)

rafaelderolez commented 3 years ago

@lorenzodejong With the example above, imageProps would still return the original height instead of the newly calculated one .height(options.width * cropRatio). This makes it so that the aspectRatio of the original image doesn't match the cropRatio previously defined, in turn making the image stretched/squished. Let me know if I'm not explaining myself properly.

piersolenski commented 3 years ago

I'm having this issue too, can you shed any more clarity on it @lorenzodejong?

lorenzodejong commented 3 years ago

It seems like your finding is correct. The hook actually returns the original image width/height in the imageProps and not the cropped one. Because the cropped image is determined from the next/image loader callback prop it's also rather hard to determine this at initial render.

For now a workaround would be to manually pass the cropped height to the component props, so for example:

const cropRatio = 1080 / 1920;

const imgOpts = (transform, options) => {
    return transform.width(options.width).height(options.width * cropRatio).fit('crop');
};

export default function Component() {
   const imageProps = useNextSanityImage(sanityCDNClient, image, { imageBuilder: imgOpts });
   return (
     <Img
         {...imageProps}
         height={imageProps.width * cropRatio}
         layout="responsive"
         sizes="(max-width: 800px) 100vw, 800px"
      />
  );
}

I'll have to dive in the implementation of this hook to come up with a fix for this. I'll leave this issue open for now to keep tracking this bug.

chris-erickson commented 3 years ago

I'm using it in a bit simpler way, and not quite sure how to map what you're saying here to my case.. ideas? Just updated to Next v12.

export const MyItem = ({ image, href }) => {
  const imageProps = useNextSanityImage(client, image);
  return (
    <Anchor href={href} label={image.altText}>
      <Img
        {...imageProps}
        {...{ alt: image.altText }}
        sizes="(max-width: 800px) 100vw, 800px"
      />
    </Anchor>
  );
};
Image with src "https://cdn.sanity.io/images/mydataset/production/818a787fc0afc4b6522de9596b80239633dee03b-2400x2400.png?w=1920&q=75&fit=clip&auto=format" has a "loader" property that does not implement width. Please implement it or use the "unoptimized" property instead.
Read more: https://nextjs.org/docs/messages/next-image-missing-loader-width

I also don't understand the implications of unoptimized: true but it does make the error go away then removes all of the srcset content so that doesn't seem like the fix I want.

lorenzodejong commented 3 years ago

@chris-erickson i guess you're missing the "layout" prop here on the next/image component, you probably want to set that to be "responsive" judging from your example. Sorry about the confusion from my previous example which also didn't include a layout prop (fixed now).

Read the documentation for more info about all the supported layout types: https://github.com/bundlesandbatches/next-sanity-image#responsive-layout

chris-erickson commented 3 years ago

Hmm, thanks for the clarification - seems to still be emitting that warning..

export const MyItem = ({ image, href }) => {
  const imageProps = useNextSanityImage(client, image);
  return (
    <Anchor href={href} label={image.altText}>
      <Img
        {...imageProps}
        {...{ alt: image.altText }}
        layout="responsive"
        sizes="(max-width: 800px) 100vw, 800px"
      />
    </Anchor>
  );
};

and here's the props making it to the Image component:

image
lorenzodejong commented 3 years ago

It seems like this has something to do with the upgrade to Next.js, we'll continue this thread in #23