Resize the image to the width it will be displayed at, then encode it at quality 80. That order matters more than either step alone. Resizing removes pixels permanently and cheaply; lowering quality damages the pixels you keep.
The measurement that settles it: one 1280 × 854 photograph, three treatments, same encoder.
| Treatment | Result | Visible quality |
|---|---|---|
| Full width, quality 80 | 67.5 KB | Excellent |
| Full width, quality 40 | 20.6 KB | Visible blocking and ringing |
| Half width (640 px), quality 80 | 16.4 KB | Excellent |
Halving the width produced a file 20% smaller than crushing the quality, and it looks like the original rather than like a bad copy. If an image is displayed at 640 pixels, storing 1280 pixels wastes bytes on detail the browser throws away during scaling.
Find the width the image is actually displayed at
Open the page, right-click the image and choose Inspect. The browser reports two numbers: the intrinsic size of the file and the rendered size in CSS pixels. If the rendered size is 640 and the intrinsic size is 3000, the file is roughly 22 times larger in pixel count than it needs to be.
Chrome and Edge flag this directly. In Lighthouse the audit is "Properly size images", and it lists every oversized file with the saving available. That list is the correct starting point for any optimisation pass, ahead of any quality change.
Multiply the rendered width by 2 for high-density screens, then stop. A 640-pixel slot needs a 1280-pixel file. It does not need a 3000-pixel file: beyond roughly 2× the returns are invisible on every shipping display.
Choose the format by what the image contains
Format matters as much as dimensions, and the right answer depends on content rather than preference. The same four test images, all at 1280 × 854, encoded three ways:
| Content | JPEG q80 | WebP q80 | PNG | Use |
|---|---|---|---|---|
| Photograph | 67.5 KB | 27.0 KB | 1274.1 KB | WebP, JPEG fallback |
| Flat graphic, hard edges | 15.4 KB | 4.2 KB | 14.6 KB | WebP, PNG fallback |
| Smooth gradient | 10.1 KB | 3.9 KB | 23.0 KB | WebP, JPEG fallback |
WebP won every category on this test set. On the photograph it produced 27.0 KB against 67.5 KB from libjpeg-turbo at the same nominal quality, at marginally higher measured fidelity. Against MozJPEG, which is the fairer comparison at 41.1 KB, WebP was 34% smaller.
Two cautions. Quality numbers are not comparable across formats, so "quality 80" means something different to each encoder. And WebP is not universally smaller: on a synthetic random-noise image it produced 725.5 KB against 680.7 KB for JPEG. Noise is rare in real photographs, but it demonstrates that the advantage is content-dependent rather than absolute.
Encode at quality 80
Quality 80 sits at the elbow of the size-versus-fidelity curve for photographic content. Above it, file size climbs steeply for fidelity nobody perceives: quality 100 costs 8.7 times the bytes of quality 80 on the test image. Below it, fidelity falls faster than size: from quality 40 to quality 30 the file shrank 17% and lost 0.59 dB.
Raise it to 85 for hero images, which are viewed larger and give artefacts more room to show. Lower it to 70 or 75 for thumbnails under 400 pixels, where the display discards detail anyway.
Set a target size rather than a fixed quality when the output must fit a limit. Content changes file size far more than the quality setting does — the same encoder at quality 80 produced 10.1 KB from a gradient and 680.7 KB from noise — so a fixed quality across a mixed batch produces wildly variable results.
Set a page weight budget and hold to it
A budget converts an open-ended optimisation into a pass or fail test. These figures are practical targets rather than standards:
- Total images per page
- Under 1 MB. A page of six photographs at 150 KB each fits comfortably.
- The largest single image
- Under 200 KB. This is usually the Largest Contentful Paint element, so it governs the metric.
- Content images in an article
- 100 KB to 150 KB each at the width they are displayed.
- Thumbnails and avatars
- Under 20 KB. At 200 pixels a photograph reaches this at quality 75 without difficulty.
- Logos and interface icons
- Under 5 KB, as SVG wherever the artwork allows it.
How images affect Largest Contentful Paint
Largest Contentful Paint records when the largest visible element finishes rendering. On most content pages that element is an image, which makes image delivery the dominant term in the metric. Google's threshold for a good score is 2.5 seconds at the 75th percentile of real visits.
Four properties of the image determine it. File size sets transfer time. Dimensions set decode time. Discovery time decides when the request starts. Render-blocking resources ahead of it delay everything.
Practical consequences, in order of effect. Resize and compress the hero image first, since it is usually both the largest file and the LCP element. Avoid lazy-loading it: loading="lazy" on an above-the-fold image delays the request and reliably worsens LCP. Add fetchpriority="high" to it instead. Keep it out of a carousel or slider that only requests the image after JavaScript executes.
Serve more than one size
A single file cannot be right for a 360-pixel phone and a 1440-pixel desktop. The srcset attribute lets the browser choose before it downloads.
<img
src="/photo-800.jpg"
srcset="/photo-400.jpg 400w, /photo-800.jpg 800w, /photo-1600.jpg 1600w"
sizes="(max-width: 700px) 100vw, 800px"
width="800" height="533"
alt="Description of the photograph">
The sizes attribute tells the browser how wide the image will be laid out before CSS has been applied. Without it the browser assumes full viewport width and picks a file larger than necessary.
Wrap the same set in <picture> to offer WebP with a JPEG fallback. Browsers take the first source they support, so no server-side detection is needed.
Always declare width and height
An image without dimensions occupies no space until it loads, then pushes the page down. That is Cumulative Layout Shift, and the target is a score below 0.1.
Set the width and height attributes to the intrinsic pixel dimensions of the file, then control display size with CSS. Modern browsers use the ratio of those two numbers to reserve the correct space before a single byte of the image arrives. The attributes do not fight your CSS; they only supply the aspect ratio.
A workflow that repeats
- Keep the original. Every step below is destructive, and you will want to redo one of them.
- Crop to the composition you want, before any resizing.
- Resize to the display width multiplied by 2. Stop there.
- Choose the format by content: WebP for almost everything, PNG when transparency or hard-edged text is involved, SVG for logos and icons.
- Encode at quality 80, or set a target size if the file must fit a limit.
- Strip metadata for web delivery. Keep the ICC profile if the image has saturated colour.
- Write the width, height and alt attributes into the markup.
- Check the result at the size it will be displayed, on a screen you have not been staring at for an hour.
How to verify the result
Reload the page with the network tab open and sort by size. Anything above 200 KB deserves an explanation. Run Lighthouse and read three audits: "Properly size images", "Efficiently encode images" and "Serve images in next-gen formats". Each names the specific file and the saving available.
Then check the field data rather than the lab score. PageSpeed Insights reports Core Web Vitals from real Chrome users when enough visits exist. Lab numbers come from one simulated device; field numbers come from your actual audience on their actual connections, and only the second kind affects anything.
Fixing images that are already published
A site with several hundred existing images needs an order of work, because the effort is not evenly rewarded.
Start by listing the largest files rather than the most numerous. Sort the media library by size, or crawl the site and sort the image responses by content length. On most sites a handful of files account for a disproportionate share of total image weight, and they are almost always full-resolution camera uploads that nobody resized.
Fix those first, then work down the list until the remaining files are under 200 KB. Stop there. Reducing a 40 KB thumbnail to 32 KB is real, but it will not change any metric a visitor experiences.
Two cautions when replacing files in place. Keep the same dimensions, or every page that hard-codes width and height will shift. And keep the same filename and URL where possible: changing an image URL loses whatever image search traffic the old URL had accumulated, and orphans it from any page that hotlinks it.
What a CDN does and does not solve
An image CDN resizes, re-encodes and format-negotiates on demand. Given one high-resolution original it serves a 400-pixel WebP to a phone and a 1600-pixel AVIF to a desktop, from an edge location near the visitor.
That removes most of the manual work described above, and it is the correct answer for a large or frequently changing library. It solves delivery.
It does not solve everything, and three problems survive it. A CDN cannot know the display width unless your markup tells it, so srcset and sizes still matter. It cannot fix an image that is the wrong format at source — a photograph uploaded as a 12 MB PNG still costs a 12 MB origin fetch and a slow first request. And it introduces a third-party dependency in the critical rendering path, which is a real trade rather than a free win.
Mistakes that undo the work
- Lazy-loading the hero image
- Applying
loading="lazy"to everything is tempting and wrong. On an above-the-fold image it delays the request until layout is computed, which reliably worsens Largest Contentful Paint. Lazy-load below the fold only. - Putting the LCP image in CSS
- A background image declared in a stylesheet is not discovered until the CSS is parsed and the rule matched. An
<img>element is discovered by the preload scanner while the HTML is still streaming. Use a real element for anything that matters. - Hiding the image behind JavaScript
- Carousels, lightboxes and lazy-loading libraries that set
srcfrom script delay the request until the bundle has downloaded, parsed and executed. On a slow connection that is seconds. - Compressing an already-compressed file
- Re-encoding a JPEG that is already at quality 80 removes little and damages more. Work from the original whenever it exists.
- Ignoring the favicon and logo
- These load on every page. A 200 KB PNG logo costs more across a session than a single oversized photograph. Use SVG.
- Optimising images while shipping a 2 MB JavaScript bundle
- Images are usually the largest share of page weight, but not always. Check the actual breakdown before deciding where the problem is.
How these numbers were measured
Source: one photograph, 1280 × 854 pixels, held as a lossless PNG. Encoders: libvips 8.17.3 through sharp 0.34.5, with libjpeg-turbo and MozJPEG for JPEG and libwebp 1.6.0 for WebP. Sizes are exact byte lengths converted at 1024 bytes per kilobyte. The resize test used a plain width reduction to 640 pixels with the default Lanczos filter, then the same quality 80 encode.
The budget figures and LCP guidance are conventions and thresholds, not measurements from this test. One photograph is not a benchmark suite; a different image moves every number, though the ordering of the three treatments holds across photographic content.