Practical Guides

How to Compress Images for a Website Without Wrecking Them

Resize before you compress. Measured on one photograph: half the width at quality 80 produced a 16.4 KB file, while full width at quality 40 produced 20.6 KB and looked far worse. The order of operations matters more than the settings.

Ahtisham Khan

Developer and semantic SEO practitioner

Published
Last updated
Updated
Reading time
8 min read

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.

TreatmentResultVisible quality
Full width, quality 8067.5 KBExcellent
Full width, quality 4020.6 KBVisible blocking and ringing
Half width (640 px), quality 8016.4 KBExcellent

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:

ContentJPEG q80WebP q80PNGUse
Photograph67.5 KB27.0 KB1274.1 KBWebP, JPEG fallback
Flat graphic, hard edges15.4 KB4.2 KB14.6 KBWebP, PNG fallback
Smooth gradient10.1 KB3.9 KB23.0 KBWebP, 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

  1. Keep the original. Every step below is destructive, and you will want to redo one of them.
  2. Crop to the composition you want, before any resizing.
  3. Resize to the display width multiplied by 2. Stop there.
  4. Choose the format by content: WebP for almost everything, PNG when transparency or hard-edged text is involved, SVG for logos and icons.
  5. Encode at quality 80, or set a target size if the file must fit a limit.
  6. Strip metadata for web delivery. Keep the ICC profile if the image has saturated colour.
  7. Write the width, height and alt attributes into the markup.
  8. 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 src from 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.

From the blog

Other guides you may find useful.

  • Image Compression

    JPEG Quality Settings Explained: What the Number Actually Means

    Quality 80 is not 80% of anything. It is an index into a quantisation table. Measured on one photograph: dropping from 100 to 90 removes 77% of the file and 4.2 dB of fidelity; dropping from 40 to 30 removes 17% and costs almost as much.

    8 min read

  • Image Formats

    WebP vs JPEG: Which Should You Use, and When JPEG Still Wins

    WebP produced a 27.0 KB file against MozJPEG's 41.1 KB on the same photograph at slightly higher measured fidelity — 34% smaller. It also produced a larger file than JPEG on random noise. The advantage is real and content-dependent.

    9 min read

  • Image Formats

    PNG vs JPEG: When to Use Each, With the File Sizes to Prove It

    PNG stored the same photograph in 1274.1 KB where JPEG needed 67.5 KB — nineteen times larger. On a flat graphic PNG won. On a smooth gradient PNG lost to JPEG by a factor of two, which surprises most people.

    9 min read