Responsive Images With Nginx on Ubuntu

I started looking into this topic because, as you’ve probably heard, Google
changed its pagespeed insights tool (and search ranking algorithm) to focus on
mobile-first. I’ve got an image-heavy blog that does pretty well in Google,
but my pagespeed score was somewhere between 75 and 80. One of Google’s
biggest complaints to me were that my images were not resized properly.

The only problem is I have hundreds of images on that blog, and there was
no way on earth I was going to actually create mobile-friendly versions of
every single image.

Nginx to the rescue! Nginx has a neat module called image_filter which
will do the work for you and resize images on the fly. This is awesome
because it means you also only resize the images people are requesting
instead of all the images.

Here’s how to achieve responsive images with Nginx.

Install the Nginx image_filter module

So, there are a number of different tutorials on how to achieve this, but
apparently none of them considered sharing exactly how you can install
this module.

If you simply run:

sudo apt-get install nginx-module-image-filter

There’s a good chance the server is going to tell you that it cannot find
this illustrious image filter module! Annoyingly,
the official Nginx documentation only explains how to achieve this using Nginx Plus, which is
apparently an enterprise version of Nginx. But there are also
other blog posts
on this topic, and surely not everyone is using Nginx Plus.

Eventually, I found a website which explained how to enable this module
by enabling additional sources in Ubuntu. The instructions are copied here:

Add the following line to /etc/apt/sources.list (the following of course depends on the Ubuntu version you are running):deb http://nginx.org/packages/ubuntu/ xenial nginx

_Note: If you are in the position of installing Nginx from scratch, apparently
it is possible to enable this module at that time. But I’m going to guess that
most of us are installing it on an existing server. _

Enable the Nginx image_filter module

Open up /etc/nginx/nginx.conf and in the outermost scope (not inside events or http
or anything like that), load the module:

Obviously you should change /opt/mywebsite/public/images/$image to be the path
to the images you already have. The important thing for later is that you
keep the path in parallel to however you access images today.

For example, if today you load images via:

www.mywebsite.com/images/file.jpg

You want to make sure that your responsive route is something like:

www.mywebsite.com/media/320/images/file.jpg

Rather than:

www.mywebsite.com/media/320/file.jpg

In which case, in the previous example, you should route to
/opt/mywebsite/public instead. At least, that’s what you want if,
like me, you’re running a statically generated site and you want to
modify the underlying shortcode you’re using for images without needing
to do string manipulation on your paths.

Add a server cache

So, both the official Nginx docs and the other blog post (which I guess is
derived from the Nginx docs) suggest that instead of just resizing the pictures
on demand, you create a second server that acts as a cache. Makes sense.
Here’s how that looks:

Here I’ve specified two widths, 640px and 320px for our images. You can obviously
add new ones, change these to whatever you want, etc.

Run nginx -t and then service nginx restart to reload the configuration.

Open up an image you have on your server to check that it’s working as
expected! For instance:

www.mywebsite.com/media/640/path/to/image.jpg

It should be resized to the proper width!

Use the responsive images

Ok, so now we need to take those images and use them! There are lot of different
ways to do responsive images apparently, but I don’t really care about them.
Here is the dead-simplest way to just get the images to load a different
resolution on smaller devices:

Implications for testing locally

So, the blog I’m running this on is using a “outdated” set of tools, like
jQuery and Gulp. But I have no interest in updating them, as it works perfectly
fine. I also don’t have proper deployments, it’s just running on a Digital
Ocean droplet somewhere, and when I run the blog locally, I’m using
Hugo’s development server, not Nginx.

So the question is: How can I get this not to break my local development
experience?

If I wanted to over-engineer it I would probably put it in Docker, but instead
I just hacked around it in the shortcode template:

Obviously this code is not super robust but…it’s a blog. So far so good ;)

Note: I did think about using query parameters for the width instead of
putting it all in the path. Unfortunately, Nginx doesn’t seem to support
extracting query parameters in the location directive. That said, I saw some
examples
of people trying to work around this
but from the comments it’s unclear if they were successful.

Results

I did a few things to improve image loading on the blog to appease Google:

Made all the images in the body of blog posts responsive

Used the resized versions in all previews of posts (like the list page or recommended posts at the bottom of an article)

Lazy loaded all images below the fold

These things put together brought the pagespeed score on my blog’s homepage
to 92! Google still hates that some of its own scripts (looking at you Google
Maps) aren’t cached long enough, but I’m finally “in the green” with Google.
Do note that I have done more than just these things to achieve this score,
such as inlining critical CSS, leveraging caching, etc.

Mobile Score

Desktop Score

I can’t speak for how this would scale, to give you an idea, my blog gets
about 30k pageviews per month / 1-1.3k per day, so it’s not massive, but it
is very image-heavy.

A quick check on the size of my cache, which lives for 24h:

du -sh /tmp/nginx-images-cache/
> 21M /tmp/nginx-images-cache/

And that’s for about 350 images (at least, I’m assuming that each of these
cache items is in fact an image, who knows what Nginx is doing inside):

find /tmp/nginx-images-cache/ -type f | wc -l
> 352

I should also mention that I have Cloudflare as a CDN in front of my website,
which caches roughly 50% of the requests that would come to my server.

I hope this helps you add responsive images to your website using Nginx!
If you have any questions, feel free to reach out on twitter
@monicalent.