{"course":{"slug":"image-editing","title":"Editing an Image","url":"https://addaly.com/learn/image-editing","level":"beginner","track":"create"},"lesson":{"slug":"serving-the-right-file","title":"One picture, six files, and letting the browser choose","url":"https://addaly.com/learn/image-editing/serving-the-right-file","position":76,"of":78,"minutes":9,"answer":"Ship several widths and formats and let the browser choose with srcset, sizes and picture (sizes being the attribute that actually prevents oversized downloads) then lazy-load everything except the hero.","keyIdea":"Ship several widths and formats and let the browser choose with srcset, sizes and picture (sizes being the attribute that actually prevents oversized downloads) then lazy-load everything except the hero, which needs high fetch priority because it is what the load-time metric is measuring."},"body":{"format":"markdown","text":"## The problem a single file cannot solve\n\nThe previous lessons give you a width for a destination. A web page has no single destination: the same image sits in a 360-pixel column on a phone, a 700-pixel column on a laptop and a 1200-pixel column on a desktop, across device pixel ratios of 1, 2 and 3.\n\nExport for the largest and you punish every phone. Export for the phone and the desktop is soft. The answer is to ship several files and describe them, letting the browser pick before it downloads anything.\n\n## srcset and sizes, and which does what\n\n```html\n<img src=\"photo-800.jpg\"\n     srcset=\"photo-400.jpg 400w, photo-800.jpg 800w, photo-1600.jpg 1600w\"\n     sizes=\"(max-width: 700px) 100vw, 700px\"\n     width=\"1600\" height=\"1067\"\n     alt=\"Brass taps on a workbench\">\n```\n\n**`srcset`** lists the files and the real pixel width of each. The `w` is a fact about the file, not a request.\n\n**`sizes`** tells the browser how wide the slot will be, in CSS pixels, at each viewport width. This is the part people leave out, and leaving it out means the browser assumes the image fills the viewport and downloads something much larger than it needs.\n\nThe browser then does the arithmetic you would have done: slot width times device pixel ratio, choose the smallest file at least that big. It may also choose a smaller one on a slow connection or when the file is already cached, which is a decision you cannot make from the server.\n\n> Figure: How the browser picks, before it downloads anything. Steps: 1. Read the sizes attribute (The width of the slot in CSS pixels at each viewport width. This is the part people omit, and omitting it is what causes oversized downloads.); 2. Multiply by the device pixel ratio (A 700-pixel slot on a ratio-2 laptop needs 1,400 real pixels. Stop at 2 for photographs; 3 costs 2.25 times the bytes for a gain at the edge of detection.); 3. Read srcset (Each entry carries the file's real pixel width. The w is a statement of fact about the file, not a request for a size.); 4. Choose the smallest file at least that wide (It may pick smaller again on a slow connection, or reuse one already cached. Those are decisions no server can make for it.); 5. Then: lazy below the fold, high priority for the hero (The top image is usually what the browser's own load-time metric is timing, so marking it lazy makes the page measurably slower. Keep width and height on every image.). Leave sizes out and the browser assumes the image fills the viewport, so it fetches something far larger than the slot and nothing visibly breaks. The attribute is written in CSS units but lives in the HTML, which is why a layout change quietly desyncs it and why most site builders now generate both from one definition.\n\n**`src`** stays as the fallback for anything that does not understand `srcset`.\n\nThe one genuine annoyance: `sizes` is written in CSS units but lives in the HTML, so it has to be kept in step with a stylesheet it cannot see. When a layout changes and nobody updates `sizes`, the wrong file is served and nothing visibly breaks. Most static site generators and frameworks now write both from one definition, which is the real fix.\n\n## Combining formats and sizes\n\n`<picture>` handles format choice, `srcset` handles size, and they compose:\n\n```html\n<picture>\n  <source type=\"image/avif\" srcset=\"p-400.avif 400w, p-800.avif 800w, p-1600.avif 1600w\" sizes=\"...\">\n  <source type=\"image/webp\" srcset=\"p-400.webp 400w, p-800.webp 800w, p-1600.webp 1600w\" sizes=\"...\">\n  <img src=\"p-800.jpg\" srcset=\"p-400.jpg 400w, p-800.jpg 800w, p-1600.jpg 1600w\" sizes=\"...\"\n       width=\"1600\" height=\"1067\" alt=\"...\">\n</picture>\n```\n\nThat is nine files for one photograph, which is why nobody writes this by hand. Generate it: a shell loop over `magick`, a `sharp` script, or whatever your site builder provides. The next module's derivative-set lesson is the production version of this.\n\n`<picture>` with `media` attributes also gives you **art direction**: a different crop for narrow screens, not merely a smaller one. A wide landscape with the subject at the left becomes an unreadable strip on a phone; a square crop centred on the subject is a different photograph and the correct one.\n\n## Doing it without generating anything\n\nAn image CDN takes one master and serves derivatives from URL parameters:\n\n```\nhttps://cdn.example.com/photo.jpg?w=800&fm=avif&q=76\n```\n\nCloudflare Images, imgix, Cloudinary and the image pipelines built into most hosting platforms all work this way, as does the free and self-hostable **imgproxy** and **Thumbor**. The saving is real and the cost is a dependency: every image on your site now loads through somebody else's service, on their pricing and their uptime. For a small site, pre-generating files at build time and serving them as static files is simpler, cheaper and faster.\n\n## Lazy loading, and the one image that must not be lazy\n\n`loading=\"lazy\"` tells the browser not to fetch an image until it is near the viewport. On a long page it is the single biggest saving available.\n\nApply it to the image at the top of the page and you make the page measurably slower. That image is usually the **Largest Contentful Paint** element (the thing whose arrival the browser's own performance metric is timing) and marking it lazy pushes its request behind others and delays the moment the page looks loaded.\n\nThe rule: everything below the fold gets `loading=\"lazy\"`; the hero gets `fetchpriority=\"high\"` and no lazy attribute. You can see the effect in any browser's developer tools under Lighthouse or Performance, free, in about thirty seconds.\n\nAnd keep `width` and `height` on every image. A lazy-loaded image with no reserved space makes the page jump as the reader scrolls, which is how somebody taps the wrong thing.\n\nToday: open a page you built, throttle the network to Slow 4G in developer tools, and reload. Watch which image arrives first. If it is a thumbnail from the footer, you have found the ordering problem this lesson is about."},"check":{"question":"A developer adds a three-width `srcset` to every article image but omits `sizes`. Phones still download the 1600-pixel file although the images sit in a 340-pixel column. Why?","options":[{"text":"Without `sizes` the browser assumes the image spans the viewport, so it asks for far more than the slot needs.","correct":true},{"text":"The `w` descriptors describe intent rather than file width, so the browser cannot rank the candidates and takes the last one listed.","correct":false,"tempting":"Misreads the descriptor: `w` states the file's actual pixel width, which is precisely what lets the browser rank them."},{"text":"`srcset` only takes effect inside a `picture` element, and a bare `img` ignores it entirely.","correct":false,"tempting":"Treats the two features as dependent, when srcset works on a plain img and picture adds format and art-direction choice."},{"text":"Phones report a device pixel ratio of 3, so the browser multiplies the column width and legitimately needs the largest file available.","correct":false,"tempting":"Applies the right arithmetic to the wrong number: three times 340 is around 1020, which would still select the 800 or a 1024 file rather than 1600."}]},"prev":{"slug":"metadata-that-should-survive","title":"The fields that say who made it, and which platforms keep them","url":"https://addaly.com/learn/image-editing/metadata-that-should-survive"},"next":{"slug":"what-the-platform-does-to-your-file","title":"Your file is re-encoded on upload, and you can plan for it","url":"https://addaly.com/learn/image-editing/what-the-platform-does-to-your-file"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'One picture, six files, and letting the browser choose', in Editing an Image. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/image-editing/serving-the-right-file (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"One picture, six files, and letting the browser choose","courseTitle":"Editing an Image","module":{"index":8,"slug":"out-of-the-editor","title":"Out of the editor: size, sharpness and the delivered file"},"answer":null,"keyIdea":"Ship several widths and formats and let the browser choose with srcset, sizes and picture (sizes being the attribute that actually prevents oversized downloads) then lazy-load everything except the hero, which needs high fetch priority because it is what the load-time metric is measuring.","hero":{"kind":"figure","figure":{"t":"figure","label":"Figure","figure":{"type":"flow","steps":[{"label":"Read the sizes attribute","detail":"The width of the slot in CSS pixels at each viewport width. This is the part people omit, and omitting it is what causes oversized downloads."},{"label":"Multiply by the device pixel ratio","detail":"A 700-pixel slot on a ratio-2 laptop needs 1,400 real pixels. Stop at 2 for photographs; 3 costs 2.25 times the bytes for a gain at the edge of detection."},{"label":"Read srcset","detail":"Each entry carries the file’s real pixel width. The w is a statement of fact about the file, not a request for a size."},{"label":"Choose the smallest file at least that wide","detail":"It may pick smaller again on a slow connection, or reuse one already cached. Those are decisions no server can make for it."},{"label":"Then: lazy below the fold, high priority for the hero","detail":"The top image is usually what the browser’s own load-time metric is timing, so marking it lazy makes the page measurably slower. Keep width and height on every image."}],"title":"How the browser picks, before it downloads anything","caption":"Leave sizes out and the browser assumes the image fills the viewport, so it fetches something far larger than the slot and nothing visibly breaks. The attribute is written in CSS units but lives in the HTML, which is why a layout change quietly desyncs it and why most site builders now generate both from one definition."},"alt":"Figure: How the browser picks, before it downloads anything. Steps: 1. Read the sizes attribute (The width of the slot in CSS pixels at each viewport width. This is the part people omit, and omitting it is what causes oversized downloads.); 2. Multiply by the device pixel ratio (A 700-pixel slot on a ratio-2 laptop needs 1,400 real pixels. Stop at 2 for photographs; 3 costs 2.25 times the bytes for a gain at the edge of detection.); 3. Read srcset (Each entry carries the file’s real pixel width. The w is a statement of fact about the file, not a request for a size.); 4. Choose the smallest file at least that wide (It may pick smaller again on a slow connection, or reuse one already cached. Those are decisions no server can make for it.); 5. Then: lazy below the fold, high priority for the hero (The top image is usually what the browser’s own load-time metric is timing, so marking it lazy makes the page measurably slower. Keep width and height on every image.). Leave sizes out and the browser assumes the image fills the viewport, so it fetches something far larger than the slot and nothing visibly breaks. The attribute is written in CSS units but lives in the HTML, which is why a layout change quietly desyncs it and why most site builders now generate both from one definition."}},"blocks":[{"t":"h2","id":"the-problem-a-single-file-cannot-solve","runs":[{"text":"The problem a single file cannot solve"}]},{"t":"p","runs":[{"text":"The previous lessons give you a width for a destination. A web page has no single destination: the same image sits in a 360-pixel column on a phone, a 700-pixel column on a laptop and a 1200-pixel column on a desktop, across device pixel ratios of 1, 2 and 3."}]},{"t":"p","runs":[{"text":"Export for the largest and you punish every phone. Export for the phone and the desktop is soft. The answer is to ship several files and describe them, letting the browser pick before it downloads anything."}]},{"t":"h2","id":"srcset-and-sizes-and-which-does-what","runs":[{"text":"srcset and sizes, and which does what"}]},{"t":"code","lang":"html","label":"HTML","text":"<img src=\"photo-800.jpg\"\n     srcset=\"photo-400.jpg 400w, photo-800.jpg 800w, photo-1600.jpg 1600w\"\n     sizes=\"(max-width: 700px) 100vw, 700px\"\n     width=\"1600\" height=\"1067\"\n     alt=\"Brass taps on a workbench\">"},{"t":"p","runs":[{"text":"srcset","b":true,"code":true},{"text":" lists the files and the real pixel width of each. The "},{"text":"w","code":true},{"text":" is a fact about the file, not a request."}]},{"t":"p","runs":[{"text":"sizes","b":true,"code":true},{"text":" tells the browser how wide the slot will be, in CSS pixels, at each viewport width. This is the part people leave out, and leaving it out means the browser assumes the image fills the viewport and downloads something much larger than it needs."}]},{"t":"p","runs":[{"text":"The browser then does the arithmetic you would have done: slot width times device pixel ratio, choose the smallest file at least that big. It may also choose a smaller one on a slow connection or when the file is already cached, which is a decision you cannot make from the server."}]},{"t":"p","runs":[{"text":"src","b":true,"code":true},{"text":" stays as the fallback for anything that does not understand "},{"text":"srcset","code":true},{"text":"."}]},{"t":"p","runs":[{"text":"The one genuine annoyance: "},{"text":"sizes","code":true},{"text":" is written in CSS units but lives in the HTML, so it has to be kept in step with a stylesheet it cannot see. When a layout changes and nobody updates "},{"text":"sizes","code":true},{"text":", the wrong file is served and nothing visibly breaks. Most static site generators and frameworks now write both from one definition, which is the real fix."}]},{"t":"h2","id":"combining-formats-and-sizes","runs":[{"text":"Combining formats and sizes"}]},{"t":"p","runs":[{"text":"<picture>","code":true},{"text":" handles format choice, "},{"text":"srcset","code":true},{"text":" handles size, and they compose:"}]},{"t":"code","lang":"html","label":"HTML","text":"<picture>\n  <source type=\"image/avif\" srcset=\"p-400.avif 400w, p-800.avif 800w, p-1600.avif 1600w\" sizes=\"...\">\n  <source type=\"image/webp\" srcset=\"p-400.webp 400w, p-800.webp 800w, p-1600.webp 1600w\" sizes=\"...\">\n  <img src=\"p-800.jpg\" srcset=\"p-400.jpg 400w, p-800.jpg 800w, p-1600.jpg 1600w\" sizes=\"...\"\n       width=\"1600\" height=\"1067\" alt=\"...\">\n</picture>"},{"t":"p","runs":[{"text":"That is nine files for one photograph, which is why nobody writes this by hand. Generate it: a shell loop over "},{"text":"magick","code":true},{"text":", a "},{"text":"sharp","code":true},{"text":" script, or whatever your site builder provides. The next module’s derivative-set lesson is the production version of this."}]},{"t":"p","runs":[{"text":"<picture>","code":true},{"text":" with "},{"text":"media","code":true},{"text":" attributes also gives you "},{"text":"art direction","b":true},{"text":": a different crop for narrow screens, not merely a smaller one. A wide landscape with the subject at the left becomes an unreadable strip on a phone; a square crop centred on the subject is a different photograph and the correct one."}]},{"t":"h2","id":"doing-it-without-generating-anything","runs":[{"text":"Doing it without generating anything"}]},{"t":"p","runs":[{"text":"An image CDN takes one master and serves derivatives from URL parameters:"}]},{"t":"code","lang":"","label":"Text","text":"https://cdn.example.com/photo.jpg?w=800&fm=avif&q=76"},{"t":"p","runs":[{"text":"Cloudflare Images, imgix, Cloudinary and the image pipelines built into most hosting platforms all work this way, as does the free and self-hostable "},{"text":"imgproxy","b":true},{"text":" and "},{"text":"Thumbor","b":true},{"text":". The saving is real and the cost is a dependency: every image on your site now loads through somebody else’s service, on their pricing and their uptime. For a small site, pre-generating files at build time and serving them as static files is simpler, cheaper and faster."}]},{"t":"h2","id":"lazy-loading-and-the-one-image-that-must-not-be-lazy","runs":[{"text":"Lazy loading, and the one image that must not be lazy"}]},{"t":"p","runs":[{"text":"loading=\"lazy\"","code":true},{"text":" tells the browser not to fetch an image until it is near the viewport. On a long page it is the single biggest saving available."}]},{"t":"p","runs":[{"text":"Apply it to the image at the top of the page and you make the page measurably slower. That image is usually the "},{"text":"Largest Contentful Paint","b":true},{"text":" element (the thing whose arrival the browser’s own performance metric is timing) and marking it lazy pushes its request behind others and delays the moment the page looks loaded."}]},{"t":"p","runs":[{"text":"The rule: everything below the fold gets "},{"text":"loading=\"lazy\"","code":true},{"text":"; the hero gets "},{"text":"fetchpriority=\"high\"","code":true},{"text":" and no lazy attribute. You can see the effect in any browser’s developer tools under Lighthouse or Performance, free, in about thirty seconds."}]},{"t":"p","runs":[{"text":"And keep "},{"text":"width","code":true},{"text":" and "},{"text":"height","code":true},{"text":" on every image. A lazy-loaded image with no reserved space makes the page jump as the reader scrolls, which is how somebody taps the wrong thing."}]},{"t":"p","runs":[{"text":"Today: open a page you built, throttle the network to Slow 4G in developer tools, and reload. Watch which image arrives first. If it is a thumbnail from the footer, you have found the ordering problem this lesson is about."}]}],"check":{"question":[{"text":"A developer adds a three-width "},{"text":"srcset","code":true},{"text":" to every article image but omits "},{"text":"sizes","code":true},{"text":". Phones still download the 1600-pixel file although the images sit in a 340-pixel column. Why?"}],"options":[{"text":[{"text":"Without "},{"text":"sizes","code":true},{"text":" the browser assumes the image spans the viewport, so it asks for far more than the slot needs."}],"correct":true,"tempting":null},{"text":[{"text":"The "},{"text":"w","code":true},{"text":" descriptors describe intent rather than file width, so the browser cannot rank the candidates and takes the last one listed."}],"correct":false,"tempting":[{"text":"Misreads the descriptor: "},{"text":"w","code":true},{"text":" states the file’s actual pixel width, which is precisely what lets the browser rank them."}]},{"text":[{"text":"srcset","code":true},{"text":" only takes effect inside a "},{"text":"picture","code":true},{"text":" element, and a bare "},{"text":"img","code":true},{"text":" ignores it entirely."}],"correct":false,"tempting":[{"text":"Treats the two features as dependent, when srcset works on a plain img and picture adds format and art-direction choice."}]},{"text":[{"text":"Phones report a device pixel ratio of 3, so the browser multiplies the column width and legitimately needs the largest file available."}],"correct":false,"tempting":[{"text":"Applies the right arithmetic to the wrong number: three times 340 is around 1020, which would still select the 800 or a 1024 file rather than 1600."}]}],"order":[1,0,2,3],"why":null},"prev":{"slug":"metadata-that-should-survive","title":"The fields that say who made it, and which platforms keep them","minutes":8},"next":{"slug":"what-the-platform-does-to-your-file","title":"Your file is re-encoded on upload, and you can plan for it","minutes":9}}}