JXL, AVIF, WebP
Serve the Optimal Image Format with Nginx
Why let Nginx determine the best image format to serve?
- There’s no need to change client-side code.
- Unlike <img> tags, <video> posters have no standard 1 way of specifying many URLs for fetching the optimal format supported by the client.
Here’s how
Upload the images with the modern extension appended.
- foo.png
- foo.png.jxl
- foo.png.avif
- foo.png.webp
By the way, here’s a script to convert them.
For dispatching the most efficient format, Nginx will inspect the
Accept
request header. For example, Chrome 85 and up advertise they support AVIF by sending its media
(MIME) type in it:
Accept: image/avif,image/webp,image/apng,image/*,*/*;q=0.8
AVIF supported: Chrome 85, Firefox 93, and Safari 16
nginx.conf 2
http { # … map $http_accept $img_ext { ~image/jxl '.jxl'; ~image/avif '.avif'; ~image/webp '.webp'; default ''; } server { # … location /images { add_header Vary Accept; try_files $uri$img_ext $uri =404; } } }
On requests within the images/
directory, the custom variable
$img_ext
gets defined with the extension associated
to the first matched MIME regex of the map
.
For that, the map uses the Accept
header ($http_accept
)
as input parameter.
Then, try_files
looks for a file with that
extension appended, such as foo.png.avif
,
and serves it if such file exists. On the other hand, the middle $uri
is
a fail-safe. For instance, maybe the AVIF isn’t uploaded yet. Also, it makes
possible to request a format directly, e.g. GET images/foo.png.webp
Lastly, Vary: Accept
is set as a response header. This
is only needed if the server has caching reverse proxies in front –
e.g., intercepting CDNs. Otherwise, those proxies would always
serve the same format sent to the first client that requested the
particular image.3
conf/mime.types
Make sure the media types are registered.
types {
# …
image/jxl jxl;
image/avif avif;
}
Open Source
The actual nginx.conf Uxtly uses.
How to test it?
#!/bin/sh img=lenna-colors.png for mime in image/avif image/png; do response_mime=$(curl -so /dev/null --head \ --header "Accept: $mime" \ --write-out "%{content_type}" $img) if [ "$response_mime" = $mime ]; then echo "OK $mime" else echo "FAIL $mime Got: $response_mime" >&2 exit 1 fi done
References
1. C. Morgan (2023) Video Poster using SVG’sforeignObject
2. E. Lazutkin (2014)
Serve files with Nginx conditionally
3. R. Mulhuijzen (2014)
Best Practices for Using the Vary Header