Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Webp images using next/image are not converted to another format on incompatible browsers #20794

Closed
JakeStanger opened this issue Jan 5, 2021 · 23 comments · Fixed by #35190
Closed
Assignees
Labels
Image (next/image) Related to Next.js Image Optimization.

Comments

@JakeStanger
Copy link

JakeStanger commented Jan 5, 2021

What version of Next.js are you using?

10.0.4

What version of Node.js are you using?

12.16.0

What browser are you using?

Safari <= 13

What operating system are you using?

MacOS, iOS

How are you deploying your application?

next start, externally hosted assets

Describe the Bug

If you pass an image src which is natively a webp to the next/image component, it is always served as a webp. Safari versions <= 13 do not include webp support so the image fails to load.

The Accept header sent in the request to the _next/image endpoint from Safari looks like this:

Accept: image/png,image/svg+xml,image/*;q=0.8,video/*;q=0.8,*/*;q=0.5

I believe the following code is at fault:

if (mimeType) {
contentType = mimeType
} else if (upstreamType?.startsWith('image/') && getExtension(upstreamType)) {
contentType = upstreamType
} else {
contentType = JPEG
}

Expected Behavior

On browsers that do not support webp, files should be converted into jpeg or png (most likely jpeg?).

To Reproduce

  • Place a .webp in your public folder or any other configured public host.
  • Use the next/image component and set the src prop to point to the .webp image.
  • Observe in Safari or IE or another non-webp supporting browser as the image is served in webp format and fails to render.

Perhaps it would also make sense to add an optional format(s) query param which the server accepts, passed through as a prop?

Also worth being aware of/considering is the HTML <Picture /> tag (MDN Docs)

Let me know if you'd like anything else from me, and I'll be happy to lend a hand :)

@JakeStanger JakeStanger added the bug Issue was opened via the bug report template. label Jan 5, 2021
@Timer Timer added kind: bug and removed bug Issue was opened via the bug report template. labels Jan 6, 2021
@Timer Timer modified the milestones: 10.x.x, backlog Jan 6, 2021
@xyy94813
Copy link

xyy94813 commented Jan 26, 2021

I try to change the local file in node_modules, it is work well.

if (mimeType) { 
   contentType = mimeType 
 } else if (upstreamType?.startsWith('image/') && getExtension(upstreamType) && getSupportedMimeType([upstreamType], req.headers.accept)) { 
   // It should be considered whether the UA supports upstreamType
   contentType = upstreamType 
 } else { 
   contentType = JPEG 
 }

Another problems: If the web image contains transparent areas, it will be problematic to convert to JPEG.

@royjosefsson
Copy link

I'm having this problem as well. I've made a temporary fix on my site until this is solved.

Hope it will be solved soon

@davidpaley
Copy link

any news about this issue?

@xyy94813
Copy link

#18646

@JakeStanger
Copy link
Author

#18646

The above PR looks like it still does not solve this issue. Correct me if I'm wrong, but if the source image is a webp it will still get served as a webp.

@xyy94813
Copy link

The current implementation logic of image-optimizer.
Correct me if I'm wrong.

If the browser supports `webp`, convert to `webp`.
  if convert to `webp` success, return `webp` format.
  else return original format.
else if original source is image type, use original source format.
else use JPEG format.

So, if you want to use webp, you original image don't need to be webp.
If your original image type is webp(or other modern image format), it will cause this issue.

Note:Animated images have additional logic

@davidpaley
Copy link

davidpaley commented Feb 22, 2021

The current implementation logic of image-optimizer.
Correct me if I'm wrong.

If the browser supports `webp`, convert to `webp`.
  if convert to `webp` success, return `webp` format.
  else return original format.
else if original source is image type, use original source format.
else use JPEG format.

So, if you want to use webp, you original image don't need to be webp.
If your original image type is webp(or other modern image format), it will cause this issue.

Note:Animated images have additional logic

For me, this is not working. I am setting the images in JPEG format, hoping that it will convert it to webp for the browsers that support it, and it is not doing it.

This is the way I am using it

import * as React from 'react'
import NextImage, { ImageProps as NextImageProps } from 'next/image'
import { preloadImage } from '../../../utils/preloadImage'
import NoImagesPlaceholder from '../../../../src/components/search/src/components/Product/assets/no-images-placeholder.jpg'

interface IImageProps {
  id?: string
  className?: string
  itemProp?: string
  title?: string
  src?: string
  alt: string
  onSuccess?: (imgUrl: string) => void
  onError?: (imgUrl: string) => void
  fallbackImage?: string
  height?: number | string
  width?: number | string
  priority?: boolean
  customPreload?: boolean
  layout?: 'responsive' | 'intrinsic' | 'fixed'
  testId?: string
}

const Image = React.forwardRef<HTMLDivElement, IImageProps>(
  (
    {
      id,
      className,
      itemProp,
      src,
      alt,
      title,
      fallbackImage,
      width,
      height,
      priority,
      customPreload,
      layout = 'fixed',
      testId,
      // CHECK WITH GABY IF HE IS STILL USING THESE PARAMETERS
      onSuccess,
      onError,
    },
    ref
  ): JSX.Element => {
    const [loading, setLoading] = React.useState(true)
    const [error, setError] = React.useState(false)

    React.useEffect(() => {
      // If an image fails to preload, handle it here in order
      // to avoid reloading it again as a broken image.
      if (!priority) {
        const preload = async () => {
          try {
            await preloadImage(src)
            setLoading(false)
            onSuccess && onSuccess(src)
          } catch (imageURL) {
            setError(true)
            onError && onError(imageURL)
          }
        }
        preload()
      }
    }, [src])

    const isFallback =
      !src || (customPreload && error) || (customPreload && loading)

    const imageProps: Omit<NextImageProps, 'layout' | 'height' | 'width'> = {
      id,
      src: isFallback ? fallbackImage || NoImagesPlaceholder : src,
      alt,
      title,
      itemProp,
      priority,
    }

    return (
      <div className={className} ref={ref} data-testid={testId}>
        {width !== undefined && height !== undefined ? (
          <NextImage
            {...imageProps}
            data-testid={isFallback ? 'fallbackImage' : 'image'}
            layout={layout}
            width={width}
            height={height}
          />
        ) : (
          <NextImage
            {...imageProps}
            data-testid={isFallback ? 'fallbackImage' : 'image'}
            layout="fill"
          />
        )}
      </div>
    )
  }
)

Image.displayName = 'Image'

And I added in the next.config.js the domains that I am using

images: {
                domains: [
                  'auth-cityfurniture.dotcmscloud.com',
                  'embed.widencdn.net',
                  'www.cityfurniture.com',
                  'cityfurniture.scene7.com',
                ],
              },
            }

@xyy94813
Copy link

“image-optimizer” confirm that the browser supports webp by explicitly specifying image/webp in Aceept.

const mimeType = getSupportedMimeType(MODERN_TYPES, headers.accept)

function getSupportedMimeType(options: string[], accept = ''): string {
const mimeType = mediaType(accept, options)
return accept.includes(mimeType) ? mimeType : ''
}

Accept: image/png, image/*; // it will not use webp, even though browser supports

@JakeStanger
Copy link
Author

JakeStanger commented Feb 23, 2021

Accept: image/png, image/*; // it will not use webp, even though browser supports

The problem here is that the same accept header will be present for browsers that do not support webp, so image/* cannot be relied on.

Perhaps instead of looking at mime types, Google's recommended test method should be employed:

// check_webp_feature:
//   'feature' can be one of 'lossy', 'lossless', 'alpha' or 'animation'.
//   'callback(feature, result)' will be passed back the detection result (in an asynchronous way!)
function check_webp_feature(feature, callback) {
    var kTestImages = {
        lossy: "UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA",
        lossless: "UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==",
        alpha: "UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA==",
        animation: "UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA"
    };
    var img = new Image();
    img.onload = function () {
        var result = (img.width > 0) && (img.height > 0);
        callback(feature, result);
    };
    img.onerror = function () {
        callback(feature, false);
    };
    img.src = "data:image/webp;base64," + kTestImages[feature];
}

check_webp_feature("lossy", res => console.log(res ? "supported" : "unsupported")

@xyy94813
Copy link

Accept: image/png, image/*; // it will not use webp, even though browser supports

The problem here is that the same accept header will be present for browsers that do not support webp, so image/* cannot be relied on.

Perhaps instead of looking at mime types, Google's recommended test method should be employed:

// check_webp_feature:

//   'feature' can be one of 'lossy', 'lossless', 'alpha' or 'animation'.

//   'callback(feature, result)' will be passed back the detection result (in an asynchronous way!)

function check_webp_feature(feature, callback) {

    var kTestImages = {

        lossy: "UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA",

        lossless: "UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==",

        alpha: "UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA==",

        animation: "UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA"

    };

    var img = new Image();

    img.onload = function () {

        var result = (img.width > 0) && (img.height > 0);

        callback(feature, result);

    };

    img.onerror = function () {

        callback(feature, false);

    };

    img.src = "data:image/webp;base64," + kTestImages[feature];

}



check_webp_feature("lossy", res => console.log(res ? "supported" : "unsupported")

This code only work well for check local env.

Note:image-optimizer is next- server's service.

@JakeStanger
Copy link
Author

This code only work well for check local env.

Note:image-optimizer is next- server's service.

Correct, but the <Image /> component code could implement this and then request a format from the back-end accordingly.

@xyy94813
Copy link

xyy94813 commented Feb 23, 2021

This code only work well for check local env.

Note:image-optimizer is next- server's service.

Correct, but the <Image /> component code could implement this and then request a format from the back-end accordingly.

Good idea.

But,can we change the default HTTP header when use img tag?

If not,i have an idea.

if browser support web.
use fetch/xmlhttprequest get binary data.
then thransform to image element locally.

Is the cost too high?

@JakeStanger
Copy link
Author

JakeStanger commented Feb 23, 2021

if browser support web.
use fetch/xmlhttprequest get binary data.
then thransform to image element locally.

There would be need to transform anything in browser (and it's definitely the wrong way to go) - just provide an Accept header to fetch, and let the back-end handle that and do the processing.

@nmokkenstorm
Copy link

Experiencing the same issue here on a production site. The strange thing is that the same version of Safari on different machines behave differently here.

@vleung-into
Copy link

@nmokkenstorm Is that due to different MacOS versions?

@Jaynam07
Copy link

Images are taking much more time to load on production - while using next/image and also not being converted to next-generation formats, What can be done in this case?

@fungilation
Copy link

Would love to have an integration solution with Vercel optimized Next/image, without resorting to a setup like https://github.com/cyrilwanner/next-optimized-images

@edlerd
Copy link

edlerd commented Sep 14, 2021

Why not use html to do the fallback. Newer browsers that support webp will load the source image url, older browsers fall back to the img tag with the jpg image.
<picture> <source srcSet="_next/image.webp?format=webp" type="image/webp" /> <img src="_next/image.webp?format=jpg" /> </picture>

@styfle styfle added the Image (next/image) Related to Next.js Image Optimization. label Nov 2, 2021
@xyy94813
Copy link

Maybe work well in Next v12.

nextConfig = {
  images: {
     // ... other conf
     format: ['image/webp', 'image/png', 'image/jpeg'],
  }
}

@kara kara assigned atcastle and unassigned kara Jan 31, 2022
@PatrickChagasTavares
Copy link

I'm having this problem as well. I've made a temporary fix on my site until this is solved.

Hope it will be solved soon

@jaddoescad
Copy link

Hi what temporary fix did you make?

@styfle
Copy link
Member

styfle commented Mar 10, 2022

This has been fixed on the canary channel.

Please give it a try with npm i next@canary or yarn add next@canary, thanks!

@github-actions
Copy link
Contributor

This closed issue has been automatically locked because it had no new activity for a month. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 10, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Image (next/image) Related to Next.js Image Optimization.
Projects
None yet