Skip to content

Commit

Permalink
fix(image): warn when animated image is missing unoptimized prop (#…
Browse files Browse the repository at this point in the history
…61045)

This PR adds a warning when attempting to optimize an animated image.

```jsx
<Image src="/image.gif" />
```

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 <Image>.
```

To remove the warning, add the `unoptimized` prop.

```jsx
<Image src="/image.gif" unoptimized />
```

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
  • Loading branch information
styfle committed Feb 28, 2024
1 parent b0474c8 commit 5a5f178
Show file tree
Hide file tree
Showing 2 changed files with 17 additions and 4 deletions.
14 changes: 10 additions & 4 deletions packages/next/src/server/image-optimizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <Image>.`
)
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(',')) {
Expand Down
7 changes: 7 additions & 0 deletions test/integration/image-optimizer/test/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 <Image>.'

export async function serveSlowImage() {
const port = await findPort()
Expand Down Expand Up @@ -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 () => {
Expand All @@ -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 () => {
Expand All @@ -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 () => {
Expand All @@ -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) {
Expand All @@ -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 () => {
Expand Down

0 comments on commit 5a5f178

Please sign in to comment.