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
.