next.js: Webp images using next/image are not converted to another format on incompatible browsers

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: https://github.com/vercel/next.js/blob/356bcdec0302c13af312ddcc5c39e78ef8e40c9d/packages/next/next-server/server/image-optimizer.ts#L234-L240

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 😃

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 21
  • Comments: 23 (2 by maintainers)

Most upvoted comments

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>

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',
                ],
              },
            }

Maybe work well in Next v12.

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

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.