Automatically Serve WebP Images with Silverstripe and NGINX
Tags: php, silverstripe, nginx, webp
Published: 2022-03-12 19:47:42

---
title: Automatically Serve WebP Images with Silverstripe and NGINX
published: true
description: How to automatically convert and serve WebP images through Silverstripe, NGINX, and PHP
tags: php,silverstripe,nginx,webp
cover_image: https://blog.theloveduckie.codes/_next/image?url=https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646473905672%2FcKVjp8Ep9.png%3Fw%3D1600%26h%3D840%26fit%3Dcrop%26crop%3Dentropy%26auto%3Dcompress%2Cformat%26format%3Dwebp&w=1920&q=75
---
## 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.](https://developers.google.com/speed/webp)
> [WebP](https://developers.google.com/speed/webp) is a modern image format that provides superior lossless and lossy compression for images on the web. Using [WebP](https://developers.google.com/speed/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](https://www.silverstripe.org/)? For those of you who don't already know...
> [Silverstripe CMS](https://www.silverstripe.org/) 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](https://docs.silverstripe.org/en/4/developer_guides/files/images/)).

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.](https://github.com/LoveDuckie/silverstripe-webp-image/blob/master/src/Flysystem/FlysystemAssetStore.php#L55)

```php
...

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.](https://docs.silverstripe.org/en/3/getting_started/installation/how_to/configure_nginx/)

```nginx
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.](https://hub.docker.com/_/nginx?tab=tags)

The most important part of this configuration is this section.
```nginx
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.
```nginx
  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.
```nginx
    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.

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1646435061471/UlG65XfkT.png)

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](https://nginx.org/). 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

- [NGINX configuration for Silverstripe with WebP image serving](https://gist.github.com/LoveDuckie/f6ed0b13041f6ee5de9cfc95b37369dc)

Thanks for reading! Feel free to [follow me on Twitter](https://twitter.com/TheLoveDuckie), or [check out my website](https://lucshelton.com/) where I often blog about other topics.