My strategy (not invented by me - found on the internet years ago) is to pretend serving JPEG & PNG only, but to actually serve WEBP and AVIF if the browser’s HTTP Accept header indicates interest.
In the NGINX http section, suffixes get generated :
http {
# ...
map $http_accept $avif_suffix {
default ".no-avif"; # try_files fails for non-existent file.jpg.no-avif
"~*avif" ".avif";
}
map $http_accept $webp_suffix {
default ".no-webp"; # try_files fails for non-existent file.jpg.no-webp
"~*webp" ".webp";
}
# ...
server {
# ...
location /choose/your/path {
location ~ \.(png|jpe?g)$ {
add_header Vary "Accept-Encoding";
log_not_found off;
expires max;
try_files $uri$avif_suffix $uri$webp_suffix $uri =404;
}
}
}
}
The HTML then only contains JPG and PNG images. The markup is already complex enough with the srcset and sizes:
<img src="foo-512.jpg"
srcset="
foo-128.jpg 128w,
foo-256.jpg 256w,
foo-384.jpg 384w,
foo-512.jpg 512w
"
sizes="
(min-width: 1280px) 6rem,
5rem"
width="512"
height="512"
alt="..."
title="..."
>
The browser might request foo-512.jpg. Depending on its Accept HTTP header, it will generate a suffix .avif or .no-avif as well as .webp or .no-webp.
In the location block, try_files will serve foo-512.jpg.avif first, if found, otherwise try foo-512.jpg.webp, and only then fall back go foo.jpg.
If AVIF is not supported by the browser as indicated by its Accept header, then try_files will look for foo-512.jpg.no-avif, which won’t exist, and go on to trying foo-512.jpg.webp and succeed (or foo-512.jpg.no-webp and fail, to then serve the actual JPG).
I’ve got a systemd scheduled task generating *.jpg.avif and *.jpg.webp from all *.jpg files, and the same for *.png.