Using content negotiation to serve WebP images in Caddy 2

Software Caddy, WebP

A while ago, I upgraded Caddy on my VPS from Caddy 1 to Caddy 2. One thing that broke during that time (and that I embarrassingly didn’t notice until today) is the content negotiation for images.

The idea of content negotiation is simple. Imagine you have a regular JPEG or PNG image on your website. If you also have a WebP version of that same image, and if the visitor’s browser can handle WebP images (which it tells us through the Accept header), then we can serve them the WebP version instead.

For my blog, that’s pretty interesting, because I always add a WebP alternative for every image if it’s smaller than the original. Take the profile picture on my front page for example: the PNG version is 53 kB (can’t use JPEG because it uses transparency)… but the WebP version is only 6 kB, as it can use lossy compression for the non-transparent part. That’s much better!

Here’s how I got it working in my Caddyfile:

schnouki.net {
    ...

	# Content negotiation: prefer WebP for images
	@acceptsWebp {
		header Accept *image/webp*
		path_regexp webp ^(.+)\.(jpg|jpeg|png)$
	}
	handle @acceptsWebp {
		@hasWebp file {re.webp.1}.webp
		rewrite @hasWebp {re.webp.1}.webp
		header @hasWebp Vary Accept
	}

    ...
}

Let’s break this down:

  • First, we check two things with @acceptsWebp: can the browser handle WebP images, and is the requested file a JPEG or PNG?
  • Then, with @hasWebp, we make sure we actually have a WebP version of that image
  • If both checks pass, we simply swap out the file extension to .webp and add a Vary: Accept header, to help with caching.

This is largely inspired by this forum thread, but I prefer to add the Vary header, as I did with Caddy 1.

With this configuration, loading the front page of my blog now transfers 56 kB of data, compared to about 100 kB before. More than enough to qualify for the 250 kB club! 🎉

Comments

Join the conversation by sending an email. Your comment will be added here and to the public inbox after moderation.