I learned something yesterday. I was thinking about how long I’ve been configuring Apache web servers. 30 years maybe? Not as often lately. But yesterday I learned that there’s a -f flag for RewriteCond.

Background

Yesterday I built a script that provides all images in the blog as AVIF and WebP. The script runs on every build, since I don’t want to have all formats in my repository. 1

I got stuck at one point: What do you actually do when AVIF or WebP aren’t smaller than the JPEG or PNG? Or when WebP is smaller than AVIF? I have to provide JPEG and PNG anyway as a fallback for really old browsers. Ignore it? Address the issue?

I opted for the latter. The trick lies in this configuration, which sits in my .htaccess.

# AVIF
RewriteCond %{HTTP_ACCEPT} image/avif
RewriteCond %{REQUEST_URI} \.(jpe?g|png)$ [NC]
RewriteCond %{REQUEST_FILENAME}.avif -f
RewriteRule ^(.+)\.(jpe?g|png)$ $1.$2.avif [T=image/avif,L]

# WebP 
RewriteCond %{HTTP_ACCEPT} image/webp
RewriteCond %{REQUEST_URI} \.(jpe?g|png)$ [NC]
RewriteCond %{REQUEST_FILENAME}.webp -f
RewriteRule ^(.+)\.(jpe?g|png)$ $1.$2.webp [T=image/webp,L]

<IfModule mod_headers.c>
  <FilesMatch "\.(jpe?g|png)$">
    Header append Vary Accept
  </FilesMatch>
</IfModule>

AddType image/webp .webp
AddType image/avif .avif

The idea behind this is to serve one of the two formats instead of a JPEG or PNG when the browser supports AVIF or WebP.

If I provided both AVIF and WebP for every file, every browser that supports both would always get AVIF. Every browser that doesn’t support AVIF but does support WebP would get WebP. And every browser that supports neither would always receive JPEG or PNG.

Regardless of whether it’s actually the smallest format. Now, all modern browsers support both AVIF and WebP. So I can choose to serve only whichever offers the greatest savings.

Smallest one wins.

So here’s what happens: The conversion script compares the file sizes of the AVIF and WebP versions and only copies the smaller one next to the original file. Next to hirseschnitzel.jpeg you’ll find either hirseschnitzel.jpeg.avif or hirseschnitzel.jpeg.webp. And only if one of the two files is actually smaller than the original.

So there’s only ever either a WebP or an AVIF. Possibly neither.

RewriteCond %{HTTP_ACCEPT} image/avif
RewriteCond %{REQUEST_URI} \.(jpe?g|png)$ [NC]
RewriteRule ^(.+)\.(jpe?g|png)$ $1.$2.avif [T=image/avif,L]

Problem: With this rule, if the AVIF is missing because the WebP was smaller, the rewrite would redirect to the void. All AVIF-capable browers would see an image.

And that’s where yesterday’s revelation came into play: How do you tell Apache to only redirect when the target file actually exists? Turns out you can do exactly that with RewriteCond %{REQUEST_FILENAME}.webp -f

RewriteCond %{HTTP_ACCEPT} image/avif
RewriteCond %{REQUEST_URI} \.(jpe?g|png)$ [NC]
RewriteCond %{REQUEST_FILENAME}.avif -f
RewriteRule ^(.+)\.(jpe?g|png)$ $1.$2.avif [T=image/avif,L]

-f … seriously … WTH … close to 30 years of Apache … and despite plenty of mod_rewrite in my .htaccess, I never once used this option. Yet there it is, plain as day in the docs. And I had to stumble across it via Google.

So here’s what happens: If an AVIF file exists, the first rewrite kicks in and the AVIF is served. If not, the second rule takes over. If a WebP file exists, the second rewrite kicks in and the WebP is served. If that file doesn’t exist either, no redirect happens and the original file is served.

This way the conversion script’s logic of only providing the smallest version actually works.

That’s what happens when you spend the last 15 years mostly dealing with mysterious _-parameters in Oracle databases or arcane corners of Solaris 11.4, even though at one point in my career I practically lived in the Apache docs.2


  1. There’s a bit of caching logic in there so that each file only gets converted once as long as it hasn’t changed. That’s also necessary because I can hear my build server when it’s converting the images. The fans make sure of that when over 300 files make their way through the converter. 

  2. Or maybe I just forgot? At this point, that’s entirely possible too … 

Mastodon · Comments
No comments yet.
Be the first to reply! ↗
Written by

Joerg Moellenkamp

Grey-haired, sometimes grey-bearded Windows dismissing Unix guy.