How to Compress Images for the Web Without Ruining Them

Your hero image is probably 3MB and it's quietly murdering your LCP score. Here's how to shrink it to under 150KB using WebP, without the 2003 flip phone vibes.

Illustration comparing a 3MB JPEG image vs a 100KB WebP image showing file size reduction for better Core Web Vitals

Here's the thing. I look at websites every single day, and the same problem shows up over and over: someone built a beautiful site, uploaded gorgeous high-resolution photos, hit publish, and now the page takes 7 seconds to load. The hero image is 4.2MB. The three feature photos are another 6MB combined. It's a complete disaster for LCP, and nobody told them.

The fix is boring and simple and works every single time. So let's get into it.

In this guide:
  1. Why images are killing your LCP score
  2. WebP vs AVIF vs JPEG: which format to use
  3. The best compression tools (free)
  4. Sizing your images correctly
  5. HTML code you need (srcset, picture element)
  6. Lazy loading: what to do and what NOT to do
  7. Image SEO: alt text, filenames, and structured data
  8. CDN: the last piece of the puzzle

Why Images Are Killing Your LCP Score

LCP (Largest Contentful Paint) measures how long until your main content is visible. And for most websites, the largest element is the hero image. So when that image is 3MB, the browser has to download all 3MB before it can paint it on screen. That takes time. On a mobile phone on a 4G connection, it takes a lot of time.

The Numbers

An average 4G mobile connection downloads around 3-4MB per second. So a 3MB hero image takes close to 1 second just to download, before any browser processing. A 150KB WebP downloads in under 50ms. That's the difference between a "poor" and "good" LCP on most devices.

Google says LCP should be under 2.5 seconds. Images are almost always the biggest chunk of that time. If your image loading time is over 1 second, your LCP is probably already in the yellow or red zone before you've even considered anything else.

The good news: image compression is the single highest-ROI fix in web performance. You can cut your LCP in half in an afternoon without touching any code, just by resizing and re-exporting your images. So let's do that.

WebP vs AVIF vs JPEG: Which Format to Use

People overthink this. Here's the practical answer:

Format File Size Quality Browser Support Best For
JPEG Baseline Good Universal Fallback only
PNG Large Excellent Universal Logos w/ transparency (only)
WebP 25-35% smaller than JPEG Excellent 98%+ browsers Everything. Photos, UI, cards.
AVIF 40-50% smaller than JPEG Excellent 90%+ browsers Photos where max compression matters
SVG Tiny Perfect (scalable) Universal Icons, logos, illustrations

My recommendation for most sites in 2026: Use WebP for photos and complex graphics. Use AVIF for hero images where you want maximum compression. Use SVG for icons and logos. Let JPEG and PNG rest in peace.

WebP has 98% browser support. There is essentially no reason to serve JPEGs to browsers that can handle WebP. If you're still worried about older browser support, use the <picture> element (more on that below) to serve AVIF to browsers that support it, WebP to the rest, and JPEG as a last fallback.

The Best Compression Tools (Free)

Squoosh.app — The One You Should Start With

Squoosh (squoosh.app) is built by Google and it's probably the best free image compression tool online. You drag in your image, choose WebP or AVIF, slide the quality control around, and see the result live with a before/after slider. It tells you the exact file size at every quality level. It's genuinely brilliant.

For a 1200x630 hero image, start at 80% WebP quality. If the result is still over 150KB, go to 75%. You'll almost certainly land under 100KB at that point, and the visual quality will be indistinguishable from the original.

Sharp — For Developers and Build Pipelines

If you're handling images programmatically, Sharp is the Node.js library to use. It's fast, it supports WebP and AVIF, and it integrates cleanly into Webpack, Vite, Next.js, and any other build tool.

const sharp = require('sharp');

sharp('hero.jpg')
  .resize(1200, 630)
  .webp({ quality: 80 })
  .toFile('hero.webp')
  .then(info => console.log('Compressed:', info.size, 'bytes'));

Cloudinary — For CMS and Non-Technical Users

If you're on WordPress, Webflow, or managing content for clients, Cloudinary handles all this automatically. Upload your image once, and Cloudinary serves it in the right format, size, and quality for every device automatically. Their free tier covers 25GB of storage and 25GB of bandwidth per month, which is plenty for a small site.

WordPress Plugins

If you're on WordPress:

Sizing Your Images Correctly

This is the thing people get wrong most often, and it's honestly more impactful than the format choice.

If your website content area is 760px wide, you should not be uploading a 2400px image. The browser will scale it down for display, but it still downloaded the entire 2400px version. That's wasted bytes every single time someone loads the page.

The rule: export at 2x the display size (for retina support), and no bigger. So if your hero image displays at 1200px wide on desktop, export it at 2400px. If it displays at 760px (article width), export it at 1520px.

For responsive sites where the image displays at different sizes on mobile vs desktop, the srcset attribute handles this automatically:

<img
  src="hero-1200.webp"
  srcset="hero-600.webp 600w,
          hero-1200.webp 1200w,
          hero-2400.webp 2400w"
  sizes="(max-width: 768px) 100vw,
         (max-width: 1200px) 80vw,
         1200px"
  alt="Descriptive alt text about what this image shows"
  width="1200"
  height="630"
  loading="eager">

The browser picks the right version based on the screen size and pixel density. Mobile users get the 600px version. Desktop retina users get the 2400px version. Everyone gets exactly what they need, nothing more.

The Picture Element — AVIF + WebP + Fallback

If you want to use AVIF where supported and fall back to WebP (and then JPEG if needed), use the <picture> element:

<picture>
  <source type="image/avif" srcset="hero.avif">
  <source type="image/webp" srcset="hero.webp">
  <img
    src="hero.jpg"
    alt="Core Web Vitals speedometer showing green LCP score"
    width="1200"
    height="630"
    loading="eager"
    decoding="async">
</picture>

The browser reads the sources in order and uses the first one it supports. AVIF-capable browsers get AVIF. WebP-capable browsers get WebP. Ancient browsers get JPEG. Clean, progressive, no JavaScript required.

Lazy Loading: What to Do and What NOT to Do

Native lazy loading (loading="lazy") is one of those genuinely good browser features that people misuse constantly.

Do this: Add loading="lazy" to any image that appears below the fold (i.e., you have to scroll to see it).

Never do this: Add loading="lazy" to your hero image, logo, or anything visible without scrolling. This actively hurts LCP because the browser will delay downloading the image until it's "needed," but by then the user is already staring at a blank space waiting for it.

<!-- Hero image: EAGER load, highest priority -->
<img src="hero.webp" alt="..." width="1200" height="630"
     loading="eager" fetchpriority="high">

<!-- Below-fold images: LAZY load -->
<img src="feature-photo.webp" alt="..." width="800" height="500"
     loading="lazy" decoding="async">

For more detail on lazy loading and the IntersectionObserver approach for more complex scenarios, check our full lazy loading guide.

Image SEO: Alt Text, Filenames, and Structured Data

Image compression is a Core Web Vitals fix, but images also have SEO value. Getting this right means Google Images traffic and better AI citation opportunities on top of faster load times.

Alt text

Every meaningful image needs an alt attribute. Not for Google bots (though they read it). For screen reader users who can't see the image. Write it to describe what's actually in the image, concisely.

Include your target keyword naturally in the alt text if the image actually represents it. Don't force it in when it doesn't fit — search engines can smell keyword stuffing from a mile away.

Filenames

Use descriptive, hyphenated filenames. core-web-vitals-lcp-speedometer.webp is infinitely better than IMG_20240312_093147.jpg. Google reads filenames. They're a weak but real signal.

Structured data for images

For article hero images, include an ImageObject in your Article schema with url, width, height, and caption. This helps Google display your image as a rich result in search. Example:

"image": {
  "@type": "ImageObject",
  "url": "https://vitalsfixer.com/blog/images/compress-images-hero.png",
  "width": 1200,
  "height": 630,
  "caption": "Illustration of a 3MB JPEG shrinking to 100KB WebP"
}

Responsive images tell Google your intent

When you use srcset, Google Discover and image search can serve the right-size image to each user. This improves your chances of appearing in Google Image results and in AI-generated summaries that include image thumbnails.

CDN: The Last Piece of the Puzzle

Even a perfectly compressed 80KB WebP image still has to travel from your server to the user's device. If your server is in Frankfurt and your user is in Sydney, that round trip adds latency that shows up in your LCP.

A CDN (Content Delivery Network) stores copies of your images on servers around the world and serves them from the nearest one. The practical result: a user in Sydney gets your image from an Australian server instead of waiting for Frankfurt.

Cloudflare (free tier) is the easiest way to add a CDN to any site. Point your DNS to Cloudflare, enable their proxy, and your static assets are cached globally. Takes about 20 minutes to set up. It also adds automatic WebP conversion through their Polish feature (paid tier), minification, and HTTP/2 — which is a whole extra layer of performance improvement on top of image compression.

The Complete Image Optimization Checklist

Real Before/After Numbers

To make this totally concrete, here's what happens when you actually do this properly on a typical landing page:

Metric Before (original JPEGs) After (WebP, compressed)
Hero image file size 3.2MB JPEG 88KB WebP
Total image page weight 8.4MB 380KB
LCP (mobile, throttled 4G) 6.8s 1.9s
PageSpeed score (mobile) 31 79

That's a real audit I ran on a real site. The only things changed were image formats and compression. No code changes, no hosting changes. Just better files.

Want to know which images are bloating your site?

Run your URL through VitalsFixer and get a specific list of which images are too large and exactly how much you can save by fixing them.

Analyze My Site Free
VF

VitalsFixer Lab

We analyze web performance for a living. The most common problem we find? Hero images that haven't been touched since 2019. This guide is the thing we send to fix that.

Keep Reading