From 5a5f178f46d6356260ee7a79c453d45158c791dc Mon Sep 17 00:00:00 2001 From: Steven Date: Tue, 23 Jan 2024 17:31:33 -0500 Subject: [PATCH] fix(image): warn when animated image is missing `unoptimized` prop (#61045) This PR adds a warning when attempting to optimize an animated image. ```jsx ``` The warning looks like the following: ``` The requested resource "/image.gif" is an animated image so it will not be optimized. Consider adding the "unoptimized" property to the . ``` To remove the warning, add the `unoptimized` prop. ```jsx ``` We don't attempt to optimized animated images because it can be very slow (30+ seconds) and sometimes deoptimizeds (the output is larger than the input) so its best to serve the animated image as-is. Closes NEXT-2199 --- packages/next/src/server/image-optimizer.ts | 14 ++++++++++---- test/integration/image-optimizer/test/util.ts | 7 +++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/next/src/server/image-optimizer.ts b/packages/next/src/server/image-optimizer.ts index eb403cc6c6b13..6e69481e36930 100644 --- a/packages/next/src/server/image-optimizer.ts +++ b/packages/next/src/server/image-optimizer.ts @@ -599,11 +599,17 @@ export async function imageOptimizer( '"url" parameter is valid but image type is not allowed' ) } - const vector = VECTOR_TYPES.includes(upstreamType) - const animate = - ANIMATABLE_TYPES.includes(upstreamType) && isAnimated(upstreamBuffer) - if (vector || animate) { + if (ANIMATABLE_TYPES.includes(upstreamType) && isAnimated(upstreamBuffer)) { + Log.warnOnce( + `The requested resource "${href}" is an animated image so it will not be optimized. Consider adding the "unoptimized" property to the .` + ) + return { buffer: upstreamBuffer, contentType: upstreamType, maxAge } + } + if (VECTOR_TYPES.includes(upstreamType)) { + // We don't warn here because we already know that "dangerouslyAllowSVG" + // was enabled above, therefore the user explicitly opted in. + // If we add more VECTOR_TYPES besides SVG, perhaps we could warn for those. return { buffer: upstreamBuffer, contentType: upstreamType, maxAge } } if (!upstreamType.startsWith('image/') || upstreamType.includes(',')) { diff --git a/test/integration/image-optimizer/test/util.ts b/test/integration/image-optimizer/test/util.ts index f3442cde830ab..dbd48adc0ca35 100644 --- a/test/integration/image-optimizer/test/util.ts +++ b/test/integration/image-optimizer/test/util.ts @@ -20,6 +20,8 @@ import type { RequestInit } from 'node-fetch' const largeSize = 1080 // defaults defined in server/config.ts const sharpMissingText = `For production Image Optimization with Next.js, the optional 'sharp' package is strongly recommended` const sharpOutdatedText = `Your installed version of the 'sharp' package does not support AVIF images. Run 'npm i sharp@latest' to upgrade to the latest version` +const animatedWarnText = + 'is an animated image so it will not be optimized. Consider adding the "unoptimized" property to the .' export async function serveSlowImage() { const port = await findPort() @@ -208,6 +210,7 @@ export function runTests(ctx) { `${contentDispositionType}; filename="animated.gif"` ) await expectWidth(res, 50, { expectAnimated: true }) + expect(ctx.nextOutput).toContain(animatedWarnText) }) it('should maintain animated png', async () => { @@ -224,6 +227,7 @@ export function runTests(ctx) { `${contentDispositionType}; filename="animated.png"` ) await expectWidth(res, 100, { expectAnimated: true }) + expect(ctx.nextOutput).toContain(animatedWarnText) }) it('should maintain animated png 2', async () => { @@ -240,6 +244,7 @@ export function runTests(ctx) { `${contentDispositionType}; filename="animated2.png"` ) await expectWidth(res, 1105, { expectAnimated: true }) + expect(ctx.nextOutput).toContain(animatedWarnText) }) it('should maintain animated webp', async () => { @@ -256,6 +261,7 @@ export function runTests(ctx) { `${contentDispositionType}; filename="animated.webp"` ) await expectWidth(res, 400, { expectAnimated: true }) + expect(ctx.nextOutput).toContain(animatedWarnText) }) if (ctx.dangerouslyAllowSVG) { @@ -282,6 +288,7 @@ export function runTests(ctx) { 'utf8' ) expect(actual).toMatch(expected) + expect(ctx.nextOutput).not.toContain('The requested resource') }) } else { it('should not allow vector svg', async () => {