Implementing WebP

What is WebP?

We use a lot of images when we build websites nowadays, and while some might disagree with this, I doubt it’s going anywhere. As a result, we have to invest a lot of work into optimizing those images as much as possible, often resorting to ugly and dated compression techniques. WebP is a new image format designed by Google for the modern web. According to a Google study, the average WebP file size is 25-34% smaller than a JPEG equivalent.

What’s the Catch?

The advantages of WebP seem obvious, lower file sizes with a negligible change in quality, with support for transparency and animation. However, as of December 2018, WebP is only supported in Chrome, Edge, and Opera (as well as their mobile equivalents). This leaves Firefox, Safari, and IE (to the surprise of nobody) with absolutely no WebP support. If you upload a WebP image to your website with no special preparation, users on those browsers will be completely unable to see the image.

Workarounds

So how do we fix this? Well, until Mozilla and Apple get around to implementing WebP in their browsers, we have to fall back to legacy formats (JPEG, PNG, GIF) for those specific browsers. Fortunately for us, we don’t need to maintain a lookup table of browsers with WebP support, the browser will tell us with every request via the HTTP Accept header:

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8

The piece of note here is the image/webp block, which will ostensibly be present in Accept headers sent by any browsers supporting WebP. Since serving WebP images will not present extra security risk, we can trust information sent by the client on this one. We still, however, need to decide how we want to handle switching image formats.

WordPress

If our site is using WordPress and we’re willing to shell out a bit of extra dough, we can use a plugin like Optimus that will automatically convert our images to WebP. This is the path of least resistance, but there are still a couple of free methods at our disposal.

.htaccess

If our web server is using Apache, this .htaccess snippet will automatically serve a WebP image for a requested JPEG or PNG should the WebP exist:

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{HTTP_ACCEPT} image/webp
    RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_URI}.webp -f
    RewriteRule (.+)\.(jpe?g|png)$ $1.$2.webp [NC,T=image/webp,E=webp:1,L]
</IfModule>

<IfModule mod_headers.c>
    Header append Vary Accept env=REDIRECT_webp
</IfModule>

AddType image/webp .webp

If we request example.com/static/image.jpg on a WebP-enabled browser, the server will instead send us the WebP file located at /webroot/static/image.jpg.webp should it exist.

PHP

We can also accomplish something similar with just PHP, but using a slightly different method:

<?php
function __w($url) {
    if(strstr($_SERVER['HTTP_ACCEPT'], 'image/webp') !== false) {
        $url .= '.webp';
    }

    return $url;
}

With this, we just need to pass the URL of the image we require to the __w function, and it’ll have the .webp suffix appended should the user’s browser support it. We do, however, still have a problem.

HTML5 <picture>

HTML5 also includes the <picture> tag, which allows you to define multiple sources for image assets. However, this approach introduces some caveats. There is some lack of overlap between support for the two features. More important is the fact that this approach does not work at all for CSS background-images or Canvas elements.


None of this is to say that these are the only ways to implement WebP; these are simply suggestions, and if you find a better, more comprehensive solution I would welcome it.

Working Around Caching

Note: This section is written with Cloudflare DNS caching in mind, but the same principles can be applied to other cache services. Cloudflare’s Pro subscription includes the Polish tool that handles WebP conversion automatically.

If our website is using a form of static caching, be it a WordPress optimizer plugin (the .htaccess process would work in this case) or a DNS cache like Cloudflare, the changes we made depending on the user’s browser may be cached and load for users on the wrong browser. Some caches will respect the HTTP Vary header, which we can set to Accept in our code to indicate that this header will alter the response. However, Cloudflare among others will not respect this header, and might serve a cached WebP-enabled response to a user on Firefox, for example. We can use different workarounds for this issue depending on our hosting situation.

Uncaching PHP

If we don’t need to cache our PHP-generated pages, we can use the following setup with Cloudflare’s page rules:

These page rules tell Cloudflare to bypass the cache for anything outside of the static directory, i.e. our PHP scripts, and use Cloudflare’s cache for anything inside of the static directory. This solution will not work with the .htaccess solution above.

Uncaching Static Assets

If we need to cache our PHP scripts, but we don’t worry too much about bandwidth from our origin server, we can uncache static assets instead by essentially doing the opposite of the previous example:

This page rule tells Cloudflare to bypass the cache only for assets inside of the wp-content/uploads directory. In contrast to the previous example, this solution will not work with the PHP solution above, or with any WordPress plugins that accomplish this by just changing links on the page.

Conclusion

WebP is still a budding image format, but it shows promise, and its substantial performance improvement can still be leveraged today for supporting audiences, with future implementations in Firefox or Safari requiring no extra development to leverage WebP functionality. With caching considerations, however, the partial switch to WebP becomes much harder, and requires a more tailored response to fit your specific hosting situation. Try converting your site’s images to WebP using the command-line utilities and see the difference for yourself.

Leave a Reply

Your email address will not be published.