Greg Rickaby

Engineering Leader / Full Stack Developer

· 9 min read · #code

Web Images

The sum of my knowledge about web-based images.

Table of Contents

Image Formats

  • WEBP - Newer format. Supports transparency, lossless quality, and animations. Supported in all modern browsers and WordPress 5.8 and above. This should be your default format.
  • AVIF - Newest format. Better than .webp in all respects, but has limited browser support. (Chrome/Opera)
  • JPG - Old and bloated. Only use as fallback or if IE11 support is required.
  • SVG - For simple geometric shapes, like icons and logos.
  • PNG - For when transparency and/or lossless quality is required.

Optimization Tools

ImageOptim (Mac/Node/CLI/Web)

ImageOptim is a great choice for optimizing PNG and JPGs. There's even a ImageOptim-CLI and online version available.

Sharp (Node/CLI)

The typical use case for Sharp is to convert large images in common formats to smaller, web-friendly JPEG, PNG, WebP and AVIF images of varying dimensions. There's even a CLI available.

Simple resize

The following example will resize all JPGs to 1920px, place them in a /thumbs directory, while preserving EXIF data:

npx sharp-cli resize '1920' --input '*.jpg' --output './thumbs' --withMetadata

Squoosh (Node/CLI/Web)

Squoosh is an open-source image compression app from Google. There's also a node-based Sqoosh CLI which is really elegant.

Simple convert

Convert all .jpg images to .webp and .avif:

npx @squoosh/cli *.jpg --avif --webp

Resize, optimize, and convert

Convert, resize, auto-optimize, and append _400 to the filename:

npx @squoosh/cli *.jpg --webp --avif auto --resize {width:400} --suffix _400

Using auto will increase the quality, but also the file size.

ImageMagick (CLI)

ImageMagick is a free and open-source CLI-based image processing tool written in C. I'd argue it's the defacto library on most web servers. It's very powerful, but the documentation is difficult to understand and the syntax is cumbersome. That said, here are some commands that I've used...

Simple resize

Resize all .jpg images to 400px wide, set the quality to 70%, append -400 to the end of the filename:

magick *.jpg -resize 400x -quality 70% -set filename:area "%t-%w" "%[filename:area].jpg"

ImageMagick will remove all EXIF data.

Use as a script

ImageMagick really shines when it's used for scripting. Below is a bash function to create some images for a hero component that I want to upload to a website. It requires the ImageOptim-CLI.

  1. Add the following to ~/.bashrc or ~/.zshrc
  2. Restart your terminal
  3. Run heroimage *.jpg

This is a destructive operation, so backup your original files first!

heroimage() {

 # Convert any PNGs to JPGs.
 magick mogrify -format jpg *.png

 # Create a list of all JPGs in the current directory.
 filelist=`ls | grep '.jpg'`

 # Loop over the list and create image "hero images" of various sizes.
 for image_file in $filelist
   imagename=`convert $image_file -format "%t" info:`
   magick convert $image_file -write +delete \
    (+clone -resize 960x540! -quality 80% -strip -insterlace Plane -set filename:filesize "%wx%h" "./$imagename-$[filename:filesize].jpg" +delete ) \
    (+clone -resize 480x270! -quality 80% -strip -insterlace Plane -set filename:filesize "%wx%h" "./$imagename-$[filename:filesize].jpg" +delete ) \
    -resize 1920x1080! \
    -quality 80% \
    -strip \
    -interlace Plane \
    -set filename:filesize "%wx%h" "./${imagename}-%[filename:filesize].jpg"

 # Run final optimizations through ImageOptim.
 imageoptim ./

WordPress Plugins

Hosted, On-demand Image Manipulation


Browser level

<img alt="A lazy loading image" src="image.webp" loading="lazy" />


  • Safari 14 and 15 have support for loading="lazy", however it must be enabled by the user. (Develop --> Experimental Features --> Lazy image loading)
  • If the image is an above-the-fold-hero, be sure to preload it.
  • Don't use loading="lazy" if the image is above the fold. (it increases LCP score)

The example below would help decrease the LCP score for hero images above the fold.

    <!-- Instruct the web browser to download this image ASAP! -->
    <link rel="preload" as="image" href="/hero-image.webp" />
    <!-- Instruct the web browser to display this image ASAP! -->
    <img alt="A hero image" src="hero-image.webp" loading="eager" />

Further reading:

Intersection Observer

  alt="I'm an image!"
  data-srcset="image-2x.webp 2x, image-1x.webp 1x"
document.addEventListener('DOMContentLoaded', function () {
  var lazyImages = []'img.lazy'))

  if ('IntersectionObserver' in window) {
    let lazyImageObserver = new IntersectionObserver(function (
    ) {
      entries.forEach(function (entry) {
        if (entry.isIntersecting) {
          let lazyImage =
          lazyImage.src = lazyImage.dataset.src
          lazyImage.srcset = lazyImage.dataset.srcset

    lazyImages.forEach(function (lazyImage) {
  } else {
    // Possibly fall back to event handlers here

Further reading:

Responsive Images

The example below will display a 400px version of the image on mobile devices, and the full size image for tablet and desktop.

  alt="an image of a thing"
  sizes="(max-width: 600px) 400px, 768px"
  srcset="image-400.webp 400w, image.webp 768w"

Further reading:

Serve Images in Modern Formats

The example below uses <picture> to serve images in the newsest formats while gracefully falling back to a .jpg.

  <!-- Try and load the .avif image -->
  <source type="image/avif" srcset="image.avif" />
  <!-- If .avif isn't supported, load the .webp image -->
  <source type="image/webp" srcset="image.webp" />
  <!-- If neither .avif nor .webp are supported, load the .jpg image -->
    alt="an image of a thing"

You can only set image attributes on the <img> tag!


  • The net gain is smaller images sizes, but at the expense of excessive DOM size. Don't be surprised if Lighthouse barks at you for having too much HTML!
  • You're also now juggling 3 different image formats for the same image. This can increase exponentially if you have multiple image sizes for each image.
  • You're better off using the responsive image systax with webp.

Further reading:

Art direction

Display a different image based on the viewport width:

<div class="align-left">
    <!-- on mobile, center and stack, display the 400px image -->
      media="(max-width: 600px)"
    <!-- on tablets, continue to center and stack, but display a higher resolution image -->
      media="(min-width: 601px) and (max-width: 1023px)"
    <!-- on desktops, align the image left, and display the 400px image again -->
      media="(min-width: 1024px)"
    <!-- for older browsers, just display a .JPG -->
      alt="an image of a thing"

Chances are the .jpg from the <img> tag wont load (unless it's an old browser), you still need to set the attributes!


  • Like with Serving Images in Modern Formats, Art Direction introduces a lot of extra HTML in the form of nested DOM elements. Lighthouse is likely to bark at you for this, so use sparingly!

Further reading:

Greg is the Director of Engineering at WebDevStudios. He also moonlights at Dummies writing and editing books. Follow him on Twitter for lots of pictures of pepperoni pizza and tidbits about Next.js.
· · ·