-
Notifications
You must be signed in to change notification settings - Fork 6
Recipe: serve WebP with nginx conditionally
WebP is a next generation image format spearheaded by Google, which provides advanced compression options. While it is so much better than legacy formats, it is only supported at the moment of writing (2/23/2014) by Chrome, and Opera on desktops and Android (see Can I use WebP image format? for more details). Firefox may support WebP in future versions.
Practical solution is to serve images conditionally depending on WebP support. This recipe discusses how to do it with nginx.
Let's assume following:
-
WebP-capable browsers advertise their capability in HTTP
Accept
header. This is how we know when we can serve WebP. -
Images we want to serve as WebP will be placed in the same directory, and have a following naming schema:
full-filename
⇒ full-filename.webp
Examples:
-
image.png
⇒image.png.webp
-
image.jpg
⇒image.jpg.webp
This way we avoid name clashes from identically named files with different file extensions.
- Not all images may have
.webp
counterparts. If it happens, we should serve original files.
Contrary to popular beliefs, conditional serving of images do not require if
nor rewrite
, and can be implemented with more lightweight map
and try_files
:
user www-data;
http {
##
# Basic Settings
##
sendfile on;
tcp_nopush on;
tcp_nodelay on;
# IMPORTANT!!! Make sure that mime.types below lists WebP like that:
# image/webp webp;
include /etc/nginx/mime.types;
default_type application/octet-stream;
gzip on;
gzip_disable "msie6";
##
# Conditional variables
##
map $http_accept $webp_suffix {
default "";
"~*webp" ".webp";
}
##
# Minimal server
##
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
root /usr/share/nginx/html;
index index.html;
# Make site accessible from http://localhost/ or whatever you like
server_name localhost;
location ~* ^/images/.+\.(png|jpg)$ {
root /home/www-data;
add_header Vary Accept;
try_files $uri$webp_suffix $uri =404;
}
}
}
For your convenience, the snippet above was placed in GitHub Gist.
Only three parts are important: mime.types
should list webp
, we should define a variable depending on Accept
header, and we should use it in try_files
.
nginx uses a file to list mappings from file extensions to MIME types. Usually it is called mime.types
, and included externally. Make sure that it lists webp
:
image/webp webp;
map
defines a variable that depends on values of other variables (see ngx_http_map_module for details). This module is included in nginx by default, you don't need to recompile anything.
map $http_accept $webp_suffix {
default "";
"~*webp" ".webp";
}
This http
-level snippet defines a variable called $webp_suffix
, which depends on $http_accept
(our HTTP Accept
header). If the header contains "webp"
substring (using a case-insensitive regular expression), than our variable will be set to ".webp"
, otherwise it will be an empty string.
Strictly speaking defining a default as an empty string is superfluous: this is the default behavior anyway. I added this excessive line here for clarity.
Interesting thing about nginx's variables is that they are all lazily calculated, so we can define a lot of them without slowing down our server --- only variables, which we actually use, will be evaluated. In our case, it means that adding our variable does not affect serving other non-image files.
This is a workhorse of the solution:
try_files $uri$webp_suffix $uri =404;
This location
-level directive checks files conditionally breaking on success:
- Checks a file + a possible
".webp"
suffix, and serves it, if it is found. - Checks a file as it was requested, and serves it, if it is found.
- Sends
HTTP404
(AKA "not found"), if not found.
Let's go over it in details. We assume that we have file called image.png
and its possible counterpart image.png.webp
.
- User comes with a WebP-capable browser:
-
$webp_suffix
is set to".webp"
. -
try_files
triesimage.png.webp
. It is served, if found. - Otherwise
try_files
triesimage.png
(the original file). It is served, if found. - Otherwise "not found" is returned.
-
- User comes with a browser that knows nothing about WebP:
-
$webp_suffix
is set to""
. -
try_files
triesimage.png
. It is served, if found. - Otherwise
try_files
tries againimage.png
(the original file). While it is clearly redundant, it is unlikely that image is not found. If this is a common situation for an application you develop, remember that nginx caches files, so it will be amortized. - Otherwise "not found" is returned.
-
try_files
is a core module directive. It may check files on disk, redirect to proxies, or internal locations, and return error codes, all in one directive. See try_files for more details.
This recipe was used as a starting point for a blog post Serve files with nginx conditionally, which contains expanded details. For example, in its appendix it explains why there is no "Recipe: serve JPEG XR with nginx conditionally".