Responsive images and performance
Images are most of a page’s weight. Learn to reserve their space, load them at the right time, and serve the size that fits.
Weight is the experience
On a typical page, images are the heaviest thing you ship — often more than all the code together. On a phone in a lift, the difference between a page that arrives and a page that does not is usually pictures. Three habits decide most of it: reserve the space, load what is needed, and send a file that fits the screen.
Reserve the space before the image arrives
Give every <img> its own width and height attributes, in pixels, matching the file. The browser reads them before the file downloads, works out the shape, and leaves exactly that much room on the page.
Without them the page does not know how tall the image will be, so it lays out the text and then shoves everything down when the image lands. That jump is why you tap a link and end up somewhere else — the page moved under your thumb while it was loading.
An image that holds its place
<img src="./starter.jpg"
alt="A jar of bubbling sourdough starter"
width="800" height="600">What each piece does
widthandheight— the file’s real pixel size. The browser reads them before the file downloads, derives the aspect ratio from the pair, and reserves exactly that much room.- Both are needed for the arithmetic: one alone describes a length, and the two together describe the shape the space must hold.
Load offscreen images later
By default the browser downloads every image on the page immediately, even the ones far below the fold. loading="lazy" changes that: the image is fetched shortly before it scrolls into view, and not before. A gallery with twelve large photographs then loads two or three instead of twelve.
The exception is worth remembering: never mark the image at the top of the page lazy. It is on screen immediately, and delaying it is the opposite of what lazy is for — that image is what the visitor is waiting to see.
A gallery that holds still
The three photographs below have no dimensions, so the page jumps when they load. Give each image its width and height, and load the two below the fold lazily.
1 left to do
- The images declare their width
- The images declare their height
- The offscreen images load lazily
width and height go on the <img> in pixels — the shape matters more than the exact numbers. loading="lazy" is the third attribute.
Serve the size that fits
An image shown 400 pixels wide does not need to be a 2000-pixel file. Below is the pattern that lets the browser choose between files you offer, using the same <picture> idea from the media chapter — here as srcset and sizes on the image itself.
The browser chooses from what you offer
srcset is a menu, not a command. Each candidate carries a descriptor — 400w says how wide the file is, 2x says it is for a screen with twice the pixel density — and sizes tells the browser how much space the image will occupy at each viewport width. With both, it can pick the smallest file that still looks right.
Without sizes, the browser assumes the image fills the viewport, which is often wrong and can make it download a larger file than the layout needs. The src attribute stays as the fallback for a browser that understands none of it, and it should name a real file.
One image, three files, one rule
<img src="./starter-800.jpg"
srcset="./starter-400.jpg 400w,
./starter-800.jpg 800w,
./starter-1600.jpg 1600w"
sizes="(max-width: 600px) 100vw, 600px"
alt="A jar of bubbling sourdough starter"
width="800" height="600"
loading="lazy">The first screen is the page
Some images are the reason the visitor came; others are further down and can wait. fetchpriority="high" on the one at the top tells the browser to spend its early bandwidth there, while loading="lazy" on the rest keeps them out of the first load. The two attributes are not opposites — they are a ranking.
decoding="async" lets the browser decode an image off the main thread, which keeps text and controls responsive while a large picture is prepared. It is a hint rather than a guarantee, but it costs nothing on an image that is not needed for the first paint.
What the Network panel shows
The Network panel is the honest version of this chapter. Reload with it open and sort by size: the largest row is usually a photograph, and its transferred size is what the reader waited for. Compare the file you offered with the size the layout actually needs — a 2000-pixel photograph shown at 400 pixels is five times the width and roughly twenty-five times the pixels it can display. Then check the timing column: an image that starts late is a lazy image doing its job, or a hero that should have been first. Numbers from the panel beat any guess about which file is heavy.
Three performance mistakes: swapping width and height, which reserves the wrong shape and makes the jump worse; marking the hero image loading="lazy", which delays the one thing the visitor is waiting for; and offering a srcset with no sizes, which leaves the browser guessing how wide the image will be. Check the Network panel, not the intention.
Try these next
- Give one image three
srcsetcandidates and asizesrule for phone and desktop. - Mark the first image
fetchpriority="high"and the rest lazy, then reload with the Network panel open. - Add
decoding="async"to a below-the-fold image and compare when the text appears.
A hero that loads first
The portrait at the top is what the page is for; the two supporting shots can wait. Give every image its width and height, load the lower two lazily, and mark the portrait high priority and decode it in the background.
1 left to do
- The images declare their width
- The images declare their height
- The supporting images load lazily
Add width and height to each <img>, loading="lazy" to the lower two, and fetchpriority="high" plus decoding="async" to the portrait.
Open the browser’s Network panel and reload. Sorting by size shows exactly which file is slowing the page down — it is almost always an image.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What do width and height on an <img> actually do?
