Guide
What size should images be on a website
Not sending pixels nobody will look at does more for how fast a page feels than the image format, the CDN or the compression setting. Here are the measured weights at five delivery widths.
What a photograph weighs
Five photographs, scaled to each delivery width, encoded at quality 75 as JPEG and as WebP:
| Width | Landscape, low detail | Portrait, high detail | Portrait, mid detail | |||
|---|---|---|---|---|---|---|
| JPEG | WebP | JPEG | WebP | JPEG | WebP | |
| 2560 px | 349 KB | 199 | 995 KB | 811 | 418 KB | 167 |
| 1920 px | 215 | 127 | 648 | 561 | 233 | 101 |
| 1280 px | 111 | 70 | 348 | 314 | 107 | 56 |
| 800 px | 53 | 35 | 154 | 144 | 50 | 30 |
| 400 px | 18 | 13 | 45 | 43 | 17 | 11 |
The spread across content is large. At 1920 px, one photograph costs 215 KB and another 648 KB: three times as much, at the same width and the same quality setting. A busy frame full of foliage and texture is expensive everywhere; a clean landscape is not. Any rule stated purely in kilobytes will be wrong for half your images.
Bytes fall more slowly than pixels
Halving the width removes 75% of the pixels. It does not remove 75% of the bytes. Going from 1920 to 960-ish on the first photograph took 215 KB to about 53 KB at 800 px, a large saving, but the ratio is consistently gentler than the pixel count suggests.
The reason is that downscaling concentrates detail. Four pixels of gently varying sky average into one pixel of sky and cost almost nothing either way, but four pixels straddling an edge average into one pixel that still has to describe that edge. The cheap information disappears faster than the expensive information, so what remains is denser per pixel.
Practical consequence: do not budget by scaling a known figure. Measure the widths you actually ship, on images that actually look like yours.
Pick widths from your layout
Start from how wide the image is actually displayed, rather than from a general figure for photo sizes. Open the page, measure the slot, and work from there.
Then multiply for screen density. A 800 px slot on a 2× display needs a 1600 px source to look sharp. Beyond 2× the returns collapse: 3× costs more than twice the bytes of 2× for a difference very few people can see, and phones on 3× screens are the ones most likely to be on a slow connection.
For most sites that lands on a small set of widths:
- 400 px for thumbnails, avatars and list items.
- 800 px for in-article images, cards and single-column phone layouts at 2×.
- 1280 px for content images on a desktop layout.
- 1920 px for full-width heroes and banners.
- 2560 px only for a full-bleed hero on a large display, and only once you have measured that it matters.
Let the browser choose
Shipping one size to everyone means either wasting bandwidth on phones or shipping something blurry to desktops. srcset exists to avoid the choice: you list the versions you have, tell the browser how wide the slot is, and it picks.
<img
src="photo-1280.jpg"
srcset="photo-400.jpg 400w,
photo-800.jpg 800w,
photo-1280.jpg 1280w,
photo-1920.jpg 1920w"
sizes="(max-width: 700px) 100vw, 700px"
width="1280" height="853"
alt="Description of the photograph">
The w values describe the files, not the display. sizes describes the slot. The browser combines them with the device pixel ratio and downloads exactly one file, before your CSS has finished loading, which is why sizes has to be stated rather than measured.
Two attributes that cost nothing
Always set width and height, even when CSS overrides them. They give the browser the aspect ratio so it can reserve the right amount of space before the image arrives. Without them, text jumps down the page when the image lands. That is the layout shift Cumulative Layout Shift measures, and it is cheap to fix.
Lazy-load everything below the fold with loading="lazy", and do not lazy-load the image at the top of the page. That image is usually your Largest Contentful Paint element, the thing the browser is being timed on, and marking it lazy delays the download until layout has been computed. Give it fetchpriority="high" instead.
A default recipe
- Generate 400, 800, 1280 and 1920 px versions of every photograph. Add 2560 only for full-bleed heroes.
- Quality 75, and check the result on the busiest image you have rather than the cleanest.
- Serve WebP where you can. On these photographs it saved between 11% and 52%.
- Wire them up with
srcsetandsizes. - Set
widthandheighton every image. fetchpriority="high"on the hero,loading="lazy"on everything else.
Then measure the result on a real page, on a throttled connection. Good Largest Contentful Paint is 2.5 seconds or less, and on an image-heavy page it is nearly always the hero image that decides it.
The one that gets forgotten
Social preview images, the picture that appears when someone shares your link, are a fixed 1200 × 630 and are fetched by crawlers rather than visitors. They are worth generating once, at that exact size, rather than pointing at a full-resolution photograph and hoping.
Scale a photo to an exact width: resize by dimensions →