Simply serving WebP images with Apache

WebP is an image format, comparable to for example JPEG en PNG, introduced by Google in 2010. It has slowly made its way into the browser ecosystem the last couple of years, up to the point where you can now almost be sort of certain that your average supports the Webp format. WebP images are smaller (in the 20-40% range) compared to their JPEG and PNG counterparts, allowing page loading times to go down. Happy users, happy sys admins.

There are multiple ways to implement this. The largest problem is that there are some (small but significant) browser groups that don’t properly display WebP images, for example all Safari browsers who run macOS before the 2020 Big Sur update. Simply replacing all image src’s in the HTML with their converted webp equivalents will almost certainly break your images for some users.

One neat trick is to leave all the HTML rendering as-is, and use Apache or Nginx to send back the webp image instead of the original jpeg or png. Every browser that supports webp sends a “image/webp” in the Accept header. The .htaccess snippet below checks the Accept header, and when image/webp is accepted and the webp image actually exists, uses that instead of the original. Most CDN’s support this behavior by serving different caches based on the Accept header.

The advantage over other solutions is that you neither have to change all <img> to <picture> elements, or have to deal with multiple cached HTML versions (one for those who support webp, and one for those who don’t). The webp conversion also optimizes the original image (in case users post non-optimized images), potentially allowing savings far beyond the original size.

For a simple implementation, one can sweep the image directory with the following command and use cwebp to convert all images. If your have less control over the installed packages, unlimited free services (they still exist!) like weserv are quite fantastic. The only downsize is that you have to host both versions, though the other alternatives suffer from that as well.

for file in *.png ; do cwebp -q 80 "$file" -o "${file%.png}.webp"; done


Example .htaccess:

<IfModule mod_headers.c>
  # Vary: Accept for all the requests to jpeg, png and gif
  Header append Vary Accept env=REQUEST_image

<IfModule mod_mime.c>
  AddType image/webp .webp

<IfModule mod_rewrite.c>
  RewriteEngine On

  # Check if browser supports WebP images
  RewriteCond %{HTTP_ACCEPT} image/webp

  # Check if WebP replacement image exists
  RewriteCond %{DOCUMENT_ROOT}/$1.webp -f

  # Serve WebP image instead
  RewriteRule (.+)\.(jpe?g|png)$ $1.webp [T=image/webp,E=REQUEST_image]