diff --git a/packages/next/next-server/server/image-optimizer.ts b/packages/next/next-server/server/image-optimizer.ts index d914aab89bec31a..83314a2af466c3c 100644 --- a/packages/next/next-server/server/image-optimizer.ts +++ b/packages/next/next-server/server/image-optimizer.ts @@ -17,9 +17,11 @@ const WEBP = 'image/webp' const PNG = 'image/png' const JPEG = 'image/jpeg' const GIF = 'image/gif' +const SVG = 'image/svg+xml' const MIME_TYPES = [/* AVIF, */ WEBP, PNG, JPEG] const CACHE_VERSION = 1 const ANIMATABLE_TYPES = [WEBP, PNG, GIF] +const VECTOR_TYPES = [SVG] export async function imageOptimizer( server: Server, @@ -200,21 +202,20 @@ export async function imageOptimizer( } } - const expireAt = maxAge * 1000 + now - let contentType: string - - if ( - upstreamType && - ANIMATABLE_TYPES.includes(upstreamType) && - isAnimated(upstreamBuffer) - ) { - if (upstreamType) { + if (upstreamType) { + const vector = VECTOR_TYPES.includes(upstreamType) + const animate = + ANIMATABLE_TYPES.includes(upstreamType) && isAnimated(upstreamBuffer) + if (vector || animate) { res.setHeader('Content-Type', upstreamType) + res.end(upstreamBuffer) + return { finished: true } } - res.end(upstreamBuffer) - return { finished: true } } + const expireAt = maxAge * 1000 + now + let contentType: string + if (mimeType) { contentType = mimeType } else if (upstreamType?.startsWith('image/') && getExtension(upstreamType)) { diff --git a/test/integration/image-optimizer/test/index.test.js b/test/integration/image-optimizer/test/index.test.js index 6dab8c60c082aab..0ac1960dc091fbb 100644 --- a/test/integration/image-optimizer/test/index.test.js +++ b/test/integration/image-optimizer/test/index.test.js @@ -73,6 +73,20 @@ function runTests({ w, isDev, domains }) { expect(isAnimated(await res.buffer())).toBe(true) }) + it('should maintain vector svg', async () => { + const query = { w, q: 90, url: '/test.svg' } + const opts = { headers: { accept: 'image/webp' } } + const res = await fetchViaHTTP(appPort, '/_next/image', query, opts) + expect(res.status).toBe(200) + expect(res.headers.get('Content-Type')).toContain('image/svg+xml') + const actual = await res.text() + const expected = await fs.readFile( + join(__dirname, '..', 'public', 'test.svg'), + 'utf8' + ) + expect(actual).toMatch(expected) + }) + it('should fail when url is missing', async () => { const query = { w, q: 100 } const res = await fetchViaHTTP(appPort, '/_next/image', query, {}) @@ -203,15 +217,6 @@ function runTests({ w, isDev, domains }) { await expectWidth(res, w) }) - it('should resize relative url with invalid accept header as svg', async () => { - const query = { url: '/test.svg', w, q: 80 } - const opts = { headers: { accept: 'image/invalid' } } - const res = await fetchViaHTTP(appPort, '/_next/image', query, opts) - expect(res.status).toBe(200) - expect(res.headers.get('Content-Type')).toBe('image/svg+xml') - await expectWidth(res, w) - }) - it('should resize relative url with invalid accept header as tiff', async () => { const query = { url: '/test.tiff', w, q: 80 } const opts = { headers: { accept: 'image/invalid' } }