Overview
Fast page load times are vital for any modern website with heavy traffic. To achieve quick response times, it often this means having to compress, minify, and cache static page assets that are served regularly on page load; including JavaScript, plain HTML, or images in particular. However, the problem with heavy compression often comes with a loss of quality in image assets, which can compromise aesthetics or styling, and detract from overall user experience.
WebP
Fortunately there's a solution to serving mostly lossless images with small payloads. Quoting from WebP's homepage.
WebP is a modern image format that provides superior lossless and lossy compression for images on the web. Using WebP, webmasters and web developers can create smaller, richer images that make the web faster.
The WebP image file format is not anything terribly new and has been for a while now, however many websites have yet to implement steps for generating.webp
images from their existing image assets.
Integrating with Silverstripe
As a case study for this blog, I've decided to talk about how to integrate the usage of this image file format with Silverstripe. What is Silverstripe? For those of you who don't already know...
Silverstripe CMS is a free and open source content management system and framework for creating and maintaining websites and web applications. It provides an out of the box web-based administration panel that enables users to make modifications to parts of the website, which includes a WYSIWYG website editor.
You can otherwise consider it as an alternative CMS to WordPress, although arguably better and less bloated. 😁 Assuming that you already are working on a Silverstripe project, you can install a convenient module that I've developed that will automatically convert any image asset that has been rasterized, resized, or scaled from your Silverstripe project (through using Silverstripe's image templating syntax).
You can install the module with the following command.
composer require loveduckie/silverstripe-webp-image
After installing this with the Composer package manager, simply run /dev/build?flush=all
. Without having done anything else, your Silverstripe project is now automatically generating and serving .webp
assets on page load! Woah! Let's take a look at how it's achieving this.
If we take a look at the repository for the module, you will see that we are using a LibGD function (made available as part of PHP) for converting PNG images when intercepting requests for serving assets. This line in particular.
...
public function createWebPImage($path, $filename, $hash, $variant = false)
{
if (function_exists('imagewebp') && function_exists('imagecreatefromjpeg') && function_exists('imagecreatefrompng') && function_exists('imagecreatefromgif')) {
$orgpath = './'.$this->getAsURL($filename, $hash, $variant);
list($width, $height, $type, $attr) = getimagesize($path);
switch ($type) {
case 2:
$img = imagecreatefromjpeg($path);
imagewebp($img, $this->createWebPName($orgpath), $this->webp_quality);
break;
case 3:
$img = imagecreatefrompng($path);
imagesavealpha($img, true); // save alphablending setting (important)
imagewebp($img, $this->createWebPName($orgpath), $this->webp_quality);
break;
}
imagedestroy($img);
}
}
...
After the conversion is made in memory (using imagewebp
), it then gets served to disk using the file-path generated by createWebPName($orgPath)
. This path is something along the lines of this.
/silverstripe-project-root/public/assets/Upload/your-image-filename.png.webp
The way that the file is named is important to note for later on.
Serving Generated WebP Images from NGINX
You can use the following NGINX configuration for serving WebP images automatically from your Silverstripe project's public assets directory. You can compare this configuration to the default one that Silverstripe has on their documentation pages for NGINX connectivity with PHP-FPM.
map $http_accept $webp_suffix
{
default "";
"~*webp" ".webp";
}
server
{
listen 80;
listen [::]:80;
server_name ${WEBSITE_DOMAIN_NAMES};
server_tokens off;
include /etc/nginx/mime.types;
...
location / {
try_files $uri /index.php?$query_string;
}
location ~* /assets/.+\.(?<extension>jpe?g|png|gif|webp)$
{
gzip_static on;
gzip_types image/png image/x-icon image/webp image/svg+xml image/jpeg image/gif;
add_header Vary Accept;
expires max;
sendfile on;
try_files "${request_uri}${webp_suffix}" $uri =404;
}
...
location /index.php {
fastcgi_read_timeout 10000;
fastcgi_buffers 4 65k;
fastcgi_buffer_size 64k;
fastcgi_busy_buffers_size 128k;
fastcgi_keep_conn on;
fastcgi_pass your-php-fpm-server-goes-here:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include /etc/nginx/fastcgi_params;
}
}
From the snippet above, ${WEBSITE_DOMAIN_NAMES}
is automatically replaced when the NGINX server starts by using envsubst
, but you can replace it with your own domain names, too. I've simply taken this configuration from my own webserver, as I am using the Alpine Linux Docker container image for NGINX.
The most important part of this configuration is this section.
map $http_accept $webp_suffix
{
default "";
"~*webp" ".webp";
}
You can think of it as a switch
statement in most languages. Depending on what $http_accept
resolves to when a request is served, will ultimately determine whether the resulting variable $webp_suffix
will resolve to either .webp
or nothing. This resulting variable will then be used when serving static files from the assets directory, which occurs in this part of the configuration.
location ~* /assets/.+\.(?<extension>jpe?g|png|gif|webp)$
{
gzip_static on;
gzip_types image/png image/x-icon image/webp image/svg+xml image/jpeg image/gif;
add_header Vary Accept;
expires max;
sendfile on;
try_files "${request_uri}${webp_suffix}" $uri =404;
}
This configuration block basically instructs NGINX to respond to requests that end with the suffix of .jpeg
, .png
, .gif
, or .webp
. It will then attempt to locate and serve the .webp
of the same requesting asset if the Accept
header has been defined to include .webp
.
You can see that happening on this line.
try_files "${request_uri}${webp_suffix}" $uri =404;
Depending on what the incoming browser request has in their Accept
header, will automatically determine if the $webp_suffix
is populated, which will in-turn decide which version of the same asset is retrieved from your server's filesystem.
Once you've applied this configuration, you can then run the following command to test it.
nginx -t
Followed by this one to reload it, assuming that the previous one did not fail.
nginx -s reload
Now when you navigate to your running Silverstripe project, you should notice something interesting if you open up your Developer Console in Chrome.
You'll notice that even though your browser is loading an image asset with a .jpeg extension, the MIME type for the response is that of image/webp
. This means that NGINX was able to locate the generated .webp
image asset, and was able to serve it back. This is good news!
Conclusion
And there you have it. An end-to-end solution for integrating .webp
image assets with your Silverstripe project, and how to serve them through a modern web-server such as NGINX. Using some clever NGINX configuration trickery, we can automatically pick and serve .webp
should it be discoverable on the filesystem. These image assets are being automatically generated by Silverstripe whenever templates are being rendered that make use of the syntax for manipulating images.
Useful Links
Thanks for reading! Feel free to follow me on Twitter, or check out my website where I often blog about other topics.
Latest comments (0)