diff --git a/packages/next/client/image.tsx b/packages/next/client/image.tsx index 43a4a32da4014..108d83258590e 100644 --- a/packages/next/client/image.tsx +++ b/packages/next/client/image.tsx @@ -167,7 +167,6 @@ function generateImgAttrs({ const last = widths.length - 1 return { - src: loader({ src, quality, width: widths[last] }), sizes: !sizes && kind === 'w' ? '100vw' : sizes, srcSet: widths .map( @@ -177,6 +176,14 @@ function generateImgAttrs({ }${kind}` ) .join(', '), + + // It's intended to keep `src` the last attribute because React updates + // attributes in order. If we keep `src` the first one, Safari will + // immediately start to fetch `src`, before `sizes` and `srcSet` are even + // updated by React. That causes multiple unnecessary requests if `srcSet` + // and `sizes` are defined. + // This bug cannot be reproduced in Chrome or Firefox. + src: loader({ src, quality, width: widths[last] }), } } diff --git a/test/integration/image-component/basic/pages/index.js b/test/integration/image-component/basic/pages/index.js index db07560c03ec0..2e35634ab8cc8 100644 --- a/test/integration/image-component/basic/pages/index.js +++ b/test/integration/image-component/basic/pages/index.js @@ -7,6 +7,13 @@ const Page = () => { return (

Hello World

+ { ) ).toBe(false) }) + it('should only be loaded once if `sizes` is set', async () => { + // Get all network requests + const resourceEntries = await browser.eval( + 'window.performance.getEntries()' + ) + + // "test-sizes.jpg" should only occur once + const requests = resourceEntries.filter((entry) => + entry.name.includes('test-sizes.jpg') + ) + expect(requests.length).toBe(1) + }) describe('Client-side Errors', () => { beforeAll(async () => { await browser.eval(`(function() {