Shrinking Blog Images: PNG vs WebP vs AVIF, Measured, and How I Cut Screenshots by 93%

How 3 phone screenshots (647KB) became 44KB: cropping, resizing, PNG color reduction, WebP, and AVIF compared by measurement at several qualities, plus serving AVIF with a WebP fallback via picture.

647KB for 3 screenshots

I decided to add 3 real screenshots to this blog's Search Console post. The phone originals looked like this:

ImageOriginal size (PNG, 1179×2556)
Not-indexed reasons table205KB
Crawled, 32 pages screen207KB
Canonical URL list235KB
Total647KB

That's nearly 650KB of images in one post, which is heavy for someone reading on mobile data. In the end, these 3 became 44KB. Here's the process, step by step, with measured numbers.

Sizes use 1KB = 1,024 bytes. Compression was done with Python Pillow 12.3; different libraries can give slightly different results per format.


Step 1: Crop to what matters

The first and easiest reduction is cropping out what you don't need. Phone screenshots include the status bar (time, battery), the browser address bar, the account avatar, and other things unrelated to the post. Much of it is personal information, so it has to go anyway.

ImageOriginalCropped (PNG)
Not-indexed reasons table205KB82KB
Crawled, 32 pages screen207KB91KB
Canonical URL list235KB152KB

Cropping alone cut the size nearly in half.


Step 2: Resize to the display size

On this blog, tall screenshots display at 380px wide. A retina screen draws 1px with 2 device pixels, so an image 760px wide, twice the display width, is enough to stay sharp. The originals were 1,133 to 1,179px wide, so there's room to shrink.

But after resizing, the PNGs got bigger:

ImageCropped PNGPNG resized to 760px
Not-indexed reasons table82KB86KB
Crawled, 32 pages screen91KB99KB
Canonical URL list152KB175KB

Resizing smooths text edges (LANCZOS resampling), which creates many in-between colors. PNG compresses best when colors are simple, so even with fewer pixels, a more complex palette can mean a bigger file. With PNG, resizing alone doesn't help; you need the next step.


Step 3: Compare formats and qualities by measuring

I saved the 760px images several ways and measured each.

MethodReasons32 pagesURL list
PNG86KB99KB175KB
PNG, 64 colors32KB34KB58KB
PNG, 32 colors28KB28KB50KB
PNG, 16 colors25KB26KB36KB
WebP lossless38KB49KB69KB
WebP q8522KB23KB43KB
WebP q7518KB18KB35KB
WebP q6016KB16KB32KB
WebP q5015KB15KB30KB
AVIF q6016KB16KB26KB
AVIF q4512KB12KB20KB

What the table shows:

  • PNG shrinks a lot with fewer colors. Screenshots use few colors, so cutting to 16 to 64 colors barely changes how they look. 86KB became 25KB. If you must use PNG, this is the most effective trick.
  • WebP lossless is bigger than you'd expect, bigger even than color-reduced PNG. For screenshots, lossy beats lossless.
  • The same quality number means different things per format. Quality scales differ between formats, so comparing "q60 to q60" is meaningless. You have to look.
  • AVIF is the smallest. At q45 it's 20 to 40% smaller than WebP q60.

Checking with your eyes

Don't pick by size alone. If text gets blurry, the screenshot loses its point. I compared the same area at 2x zoom.

The same screen saved as PNG 16 colors 26.1KB, WebP q50 14.8KB, WebP q60 16.0KB, and AVIF q45 12.4KB, zoomed 2x and stacked for comparison. Date text on a dark gray background and the text of a white button are visible. The UI is in Korean.
The same area zoomed 2x. This comparison image is saved losslessly so compression artifacts show as they are

To be honest, all four are readable. The differences are small. Looking closely, WebP q50 is very slightly hazy around the white button's text, while AVIF q45 is the smallest and still clean. So I chose AVIF q45.


Step 4: Use AVIF, fall back to WebP

Every modern browser supports AVIF, but Safari only since 16 (iOS 16). For older devices, I keep a WebP too. With HTML's <picture>, the browser picks the first format it can open.

<picture>
  <source srcset="/images/posts/shot.avif" type="image/avif" />
  <img src="/images/posts/shot.webp" alt="..." width="380" height="427" loading="lazy" decoding="async" />
</picture>

Browsers that support AVIF get the <source> AVIF; others get the <img> WebP. Opening the page in Chromium on the dev server, all 3 loaded as AVIF.

Gotcha: if the <source> file is missing, the image breaks. It's easy to assume "if there's no AVIF, it'll fall back to WebP," but it won't. The browser picks a <source> by its type alone. If that file 404s, it does not fall back to <img>; it shows a broken image. I removed the AVIF on purpose and opened it in Chromium, and got exactly that. So always create AVIF and WebP as a pair.

Making it work in Markdown

This blog writes posts in Markdown with Nuxt Content. I made a custom component for Markdown images (![alt](url)) that automatically adds the same-named .avif when you use a .webp.

![alt text](/images/posts/shot.webp "caption"){width="380" height="427"}
<!-- components/content/ProseImg.vue (condensed) -->
<template>
  <figure>
    <picture>
      <source v-if="avifSrc" :srcset="avifSrc" type="image/avif" />
      <img :src="src" :alt="alt" :width="width" :height="height" loading="lazy" decoding="async" />
    </picture>
    <figcaption v-if="title">{{ title }}</figcaption>
  </figure>
</template>

<script setup>
const props = defineProps({ src: String, alt: String, title: String, width: [String, Number], height: [String, Number] });

const avifSrc = computed(() =>
  props.src.endsWith(".webp") && !props.src.endsWith(".lossless.webp")
    ? props.src.replace(/\.webp$/, ".avif")
    : null
);
</script>

Images that must show compression artifacts as they are, like the comparison above, stay as lossless WebP only (name.lossless.webp) and skip the AVIF lookup.

By default, Nuxt Content renders Markdown images with @nuxt/image's NuxtImg. This blog deploys to Vercel, where images then go through Vercel's image transformation. That gives no guarantee the file I compressed is the one served, so I didn't use it.


Step 5: Load late, reserve space early

Besides file size, when an image downloads matters too.

AttributeWhat it does
loading="lazy"Doesn't download the image until it's near the screen
decoding="async"Keeps image decoding from blocking text rendering
width, heightReserves space before the image arrives, so text doesn't jump down when it loads

On the dev server, opening the post downloaded only the first image, and the other 2 loaded as I scrolled near them.

Put the display size in width/height. The file is 760×853px but displays at 380px wide, so width="380" height="427". With max-width: 100%; height: auto in CSS, it shrinks proportionally on narrower screens like phones.


Results

ImageOriginalFinal (AVIF q45)Reduction
Not-indexed reasons table205KB12KB94%
Crawled, 32 pages screen207KB12KB94%
Canonical URL list235KB20KB92%
Total647KB44KB93%

The WebP q60 fallback for browsers without AVIF totals 64KB (a 90% reduction).

The conversion code I used

I cropped, resized, and saved both formats with Python Pillow.

from PIL import Image

im = Image.open("screenshot.png").convert("RGB")
im = im.crop((23, 913, 1156, 2184))             # step 1: keep what matters (left, top, right, bottom)
w = 760                                          # step 2: twice the 380px display width
im = im.resize((w, round(im.height * w / im.width)), Image.LANCZOS)

im.save("shot.avif", "AVIF", quality=45)         # steps 3-4: the default
im.save("shot.webp", "WEBP", quality=60, method=6)  # for browsers without AVIF

Summary

StepEffect
Crop to what mattersRemoves personal info and cuts size nearly in half
Resize to 2x the display sizeSharp on retina. But PNG alone can get bigger from this
Measure formats and qualitiesDon't trust numbers alone; zoom in and check the text
AVIF + WebP via <picture>Use the smallest format and still support older browsers. Always ship both files
loading="lazy", width/heightDownload when needed, and keep the text from jumping

There are plenty of image compression tools, but you only know what's smallest for your images by measuring. For screenshots like these, "PNG with fewer colors" was stronger than expected and "WebP lossless" weaker. Photos would likely tell a different story.

References