diff --git a/docs/02-app/02-api-reference/01-components/image.mdx b/docs/02-app/02-api-reference/01-components/image.mdx index 7397926ad9..86871f4dd9 100644 --- a/docs/02-app/02-api-reference/01-components/image.mdx +++ b/docs/02-app/02-api-reference/01-components/image.mdx @@ -243,7 +243,7 @@ placeholder = 'empty' // "empty" | "blur" | "data:image/..." A placeholder to use while the image is loading. Possible values are `blur`, `empty`, or `data:image/...`. Defaults to `empty`. -When `blur`, the [`blurDataURL`](#blurdataurl) property will be used as the placeholder. If `src` is an object from a [static import](/docs/app/building-your-application/optimizing/images#local-images) and the imported image is `.jpg`, `.png`, `.webp`, or `.avif`, then `blurDataURL` will be automatically populated. +When `blur`, the [`blurDataURL`](#blurdataurl) property will be used as the placeholder. If `src` is an object from a [static import](/docs/app/building-your-application/optimizing/images#local-images) and the imported image is `.jpg`, `.png`, `.webp`, or `.avif`, then `blurDataURL` will be automatically populated, except when the image is detected to be animated. For dynamic images, you must provide the [`blurDataURL`](#blurdataurl) property. Solutions such as [Plaiceholder](https://github.com/joe-bell/plaiceholder) can help with `base64` generation. diff --git a/errors/placeholder-blur-data-url.mdx b/errors/placeholder-blur-data-url.mdx index 61e7d35355..3737dfae70 100644 --- a/errors/placeholder-blur-data-url.mdx +++ b/errors/placeholder-blur-data-url.mdx @@ -8,10 +8,10 @@ You are attempting use the `next/image` component with `placeholder=blur` proper The `blurDataURL` might be missing because you're using a string for `src` instead of a static import. -Or `blurDataURL` might be missing because the static import is an unsupported image format. Only jpg, png, webp, and avif are supported at this time. +Or `blurDataURL` might be missing because the static import is an unsupported image format. Only jpg, png, webp, and avif are supported at this time, though animated images are not supported. ## Possible Ways to Fix It - Add a [`blurDataURL`](/docs/pages/api-reference/components/image#blurdataurl) property, the contents should be a small Data URL to represent the image -- Change the [`src`](/docs/pages/api-reference/components/image#src) property to a static import with one of the supported file types: jpg, png, or webp +- Change the [`src`](/docs/pages/api-reference/components/image#src) property to a static import with one of the supported file types: jpg, png, webp, or avif (animated images not supported) - Remove the [`placeholder`](/docs/pages/api-reference/components/image#placeholder) property, effectively no blur effect diff --git a/packages/next/src/build/webpack/loaders/next-image-loader/blur.ts b/packages/next/src/build/webpack/loaders/next-image-loader/blur.ts index 46efcbee16..b7dbb25086 100644 --- a/packages/next/src/build/webpack/loaders/next-image-loader/blur.ts +++ b/packages/next/src/build/webpack/loaders/next-image-loader/blur.ts @@ -37,7 +37,7 @@ export async function getBlurImage( let blurWidth: number = 0 let blurHeight: number = 0 - if (VALID_BLUR_EXT.includes(extension)) { + if (VALID_BLUR_EXT.includes(extension) && !isAnimated(content)) { // Shrink the image's largest dimension if (imageSize.width >= imageSize.height) { blurWidth = BLUR_IMG_SIZE @@ -65,18 +65,15 @@ export async function getBlurImage( blurDataURL = url.href.slice(prefix.length) } else { const resizeImageSpan = tracing('image-resize') - const resizedImage = await resizeImageSpan.traceAsyncFn(() => { - if (isAnimated(content)) { - return content - } - return optimizeImage({ + const resizedImage = await resizeImageSpan.traceAsyncFn(() => + optimizeImage({ buffer: content, width: blurWidth, height: blurHeight, contentType: `image/${extension}`, quality: BLUR_QUALITY, }) - }) + ) const blurDataURLSpan = tracing('image-base64-tostring') blurDataURL = blurDataURLSpan.traceFn( () => diff --git a/packages/next/src/client/legacy/image.tsx b/packages/next/src/client/legacy/image.tsx index 569dd05e8a..d1456477ba 100644 --- a/packages/next/src/client/legacy/image.tsx +++ b/packages/next/src/client/legacy/image.tsx @@ -807,7 +807,7 @@ export default function Image({ - Add a "blurDataURL" property, the contents should be a small Data URL to represent the image - Change the "src" property to a static import with one of the supported file types: ${VALID_BLUR_EXT.join( ',' - )} + )} (animated images not supported) - Remove the "placeholder" property, effectively no blur effect Read more: https://nextjs.org/docs/messages/placeholder-blur-data-url` ) diff --git a/packages/next/src/shared/lib/get-img-props.ts b/packages/next/src/shared/lib/get-img-props.ts index a6144ebe9b..ee01d1482a 100644 --- a/packages/next/src/shared/lib/get-img-props.ts +++ b/packages/next/src/shared/lib/get-img-props.ts @@ -493,7 +493,7 @@ export function getImgProps( - Add a "blurDataURL" property, the contents should be a small Data URL to represent the image - Change the "src" property to a static import with one of the supported file types: ${VALID_BLUR_EXT.join( ',' - )} + )} (animated images not supported) - Remove the "placeholder" property, effectively no blur effect Read more: https://nextjs.org/docs/messages/placeholder-blur-data-url` ) diff --git a/test/unit/next-image-loader/get-blur-image.test.ts b/test/unit/next-image-loader/get-blur-image.test.ts new file mode 100644 index 0000000000..f958694a93 --- /dev/null +++ b/test/unit/next-image-loader/get-blur-image.test.ts @@ -0,0 +1,38 @@ +/* eslint-env jest */ +import { getBlurImage } from 'next/dist/build/webpack/loaders/next-image-loader/blur' +import { readFile } from 'fs-extra' +import { join } from 'path' + +const getImage = (filepath) => readFile(join(__dirname, filepath)) + +const tracing = () => ({ + traceFn: (fn, ...args) => fn(...args), + traceAsyncFn: (fn, ...args) => fn(...args), +}) + +const context = { basePath: '', outputPath: '', isDev: false, tracing } + +describe('getBlurImage', () => { + it('should return image for jpg', async () => { + const buffer = await getImage('./images/test.jpg') + const result = await getBlurImage( + buffer, + 'jpeg', + { width: 400, height: 400 }, + context + ) + expect(result).toBeObject() + expect(result.dataURL).toBeString() + }) + it('should return undefined for animated webp', async () => { + const buffer = await getImage('./images/animated.webp') + const result = await getBlurImage( + buffer, + 'webp', + { width: 400, height: 400 }, + context + ) + expect(result).toBeObject() + expect(result.dataURL).toBeUndefined() + }) +}) diff --git a/test/unit/next-image-loader/images/animated.webp b/test/unit/next-image-loader/images/animated.webp new file mode 100644 index 0000000000..7a1d3fe146 Binary files /dev/null and b/test/unit/next-image-loader/images/animated.webp differ diff --git a/test/unit/next-image-loader/images/test.jpg b/test/unit/next-image-loader/images/test.jpg new file mode 100644 index 0000000000..d536c88241 Binary files /dev/null and b/test/unit/next-image-loader/images/test.jpg differ