Speeding Up a Simple Static Site (With Help From Cloudinary!)

Diagnosing The Patient

For the last 2 years, I’ve run Dev Empathy Book Club, and the site hasn’t changed much. I’ve tried to keep it low-effort so I can focus on the community and the content we’re producing. One casualty of this was that the site, while simple, wasn’t very performant. (Google’s PageSpeed Insights gave it a very low score of 30/100 on mobile.)

I recently began working at Cloudinary, and I realized it’s pretty embarrassing that, as an employee of a company whose product is all about optimizing media on the web, I have a personal site that does a terrible job of it.

The final bit of encouragement came from fellow Cloudinarian Eric Portis, who published an article about Website Speed Test, a free tool from Cloudinary to grade image performance on your site. When I ran it against the Dev Empathy Book Club site, I saw that users had to download 1.5MB, which could be optimized down to 370kB, i.e. about ¼ of their weight. I also knew these images were being served directly from GitHub Pages, without any CDN, so on mobile devices the page load was pretty slow.

On top of all this, there was a good amount of render-blocking JS and CSS being downloaded without a CDN.

All this meant slower load times, and lower scores in search results. There was no good reason for it, except that I didn’t have the know-how to improve things, or the time to learn how to do it.

Enter Cloudinary

Cloudinary is a robust but easy-to-use service to upload, transform, and serve images and videos. The free tier contains way more than you’ll ever need for a simple static site, so it’s a great choice for e.g. personal sites with a few images you’d like to serve efficiently.

One awesome feature of Cloudinary which made this incredibly simple is the ability to auto-fetch images.

For example, consider this URL:

1
https://res.cloudinary.com/caplan/image/fetch/https://amcaplan.ninja/images/ninja-cropped.png

The URL consists of

1
https://res.cloudinary.com/caplan/image/fetch/

which tells Cloudinary you want to fetch an image for the caplan cloud (you create a cloud with a unique identifier when you sign up for Cloudinary), and the rest is the URL where the image can be found:

1
https://amcaplan.ninja/images/ninja-cropped.png

When you hit this URL, Cloudinary will fetch the image in the background, and begin serving via CDN.

Theoretically we could take all the images on the site and preface with the fetch incantation, but there’s a better way. Cloudinary has another feature called Auto Upload, which lets you create folders which are proxies for web locations. So if we create a ninja_images directory mapped to https://amcaplan.ninja/images/, the URL looks like this:

1
https://res.cloudinary.com/caplan/image/upload/ninja-images/ninja-cropped.png

Much better! Here’s the result:

ninja image served via Cloudinary

Now comes the fun part.

Cloudinary lets you edit images by adding transformations right into the URL. For example, by adding /w_100 before the image location, we creates a 100-pixel-wide version of the same image:

1
https://res.cloudinary.com/caplan/image/upload/w_100/ninja-images/ninja-cropped.png

small ninja image

You can crop, set gravity (focusing on a region of the image or on human faces), scale, add text layers or image overlays, and do a whole bunch more awesome stuff, just by adding to the URL.

This opens up the opportunity to create multiple versions for various breakpoints, driven via CSS. So if you take a large version as the original, you can tell Cloudinary to crop/scale the image as you see fit, no Photoshop skills required!

As one concrete example, here’s a large image for wide screens:

1
https://res.cloudinary.com/dev-empathy-book-club/image/upload/f_auto,q_auto/site/slider-bg.jpg

large image of girls holding hands

You’ll notice a couple transformations here: f_auto, which chooses the most bandwidth-optimized image format for the user’s browser, and q_auto, which reduces image size by degrading image quality without being noticeable to the human eye. Those 2 transformations already reduce the image size from 874kB to 385kB, without any noticeable difference to the user!

But we can do better on mobile, where this many pixels still aren’t helping anyone. Here’s a scaled-down version for mobile:

1
https://res.cloudinary.com/dev-empathy-book-club/image/upload/f_auto,q_auto,w_480,h_800,c_lfill,g_auto/site/slider-bg.jpg

small image of girls holding hands

In this case, we’re creating a tall image bounded at 480px width, centered on what Cloudinary determines to be the most interesting part of the image, and using a fill approach to the crop (expressed as c_lfill) to ensure we cover the entire requested dimensions of 480x800.

There are many parameters and even more options for those parameters, but the documentation is quite thorough and the system is really powerful.

To see a real-life example for what this might look like, check out the CSS for Dev Empathy Book Club’s site on GitHub.

The Gravatar Challenge

At first I assumed that Gravatars (we display a few) on the site would work the same way, but I soon realized there is a big problem with Gravatar. The URL for an image looks something like this:

1
http://secure.gravatar.com/avatar/7b5a451ee25044b9c869e3e98b79425d.jpg?s=200

with this result:

Ariel Caplan 200-pixel gravatar

If I want a larger version, I just change the s query param. So for a 400px square, I’d use this URL:

1
http://secure.gravatar.com/avatar/7b5a451ee25044b9c869e3e98b79425d.jpg?s=400

Ariel Caplan 400-pixel gravatar

Lacking the s parameter, Gravatar defaults to an 80px square:

1
http://secure.gravatar.com/avatar/7b5a451ee25044b9c869e3e98b79425d.jpg

Ariel Caplan 80-pixel gravatar

If you try to fetch a large Gravatar avatar with Cloudinary, here’s the result:

1
https://res.cloudinary.com/caplan/image/fetch/https://secure.gravatar.com/avatar/7b5a451ee25044b9c869e3e98b79425d.jpg?s=400

Ariel Caplan 80-pixel gravatar fetched through Cloudinary

What happened? Cloudinary treats ?s=400 as a meaningless parameter passed to Cloudinary, and doesn’t forward it to Gravatar.

This can be fixed, though, by URL-encoding the ? character as %3F, like so:

1
https://res.cloudinary.com/caplan/image/fetch/https://secure.gravatar.com/avatar/7b5a451ee25044b9c869e3e98b79425d.jpg%3Fs=400

Ariel Caplan 80-pixel gravatar fetched through Cloudinary

This technique should work for any characters you might need to include in the fetch URL.

However, that’s not the end of the story. What happens when someone updates their Gravatar image? Ideally, it would get updated on our site, too. But on the free plan, fetched images never change. (They can be configured to be updated on paid plans.)

It turns out that someone at Cloudinary thought of this, and therefore built Gravatar support directly into the platform. Unlike the fetch and upload image types we’ve seen so far, there’s also a gravatar image type which knows how to source a high-quality image from Gravatar, and update it automatically, with a small delay, when someone changes their avatar! (There are similar systems for other social networks, including Facebook, Google+, Instagram, and Twitter.)

If you fetch images via Gravatar in this way, you can easily scale up or down using the normal h_ and w_ parameters. So here’s that same 400px image of yours truly, fetched via Cloudinary:

1
https://res.cloudinary.com/caplan/image/gravatar/w_400/7b5a451ee25044b9c869e3e98b79425d.jpg

Ariel Caplan 400-pixel gravatar fetched through Cloudinary

Of course, once you’ve done this, you can use f_auto and q_auto to optimize images further and reduce bandwidth use. Neat!

Not Just for Images!

One little-known fact about Cloudinary: They can serve anything via CDN, not just images and video! So if you have JS or CSS files, you can serve them through Cloudinary’s CDN in the same fashion as mentioned above for images: Set up an Auto Upload folder and reference those URLs instead of the place where they’re hosted on your site. So for example, instead of:

1
https://devempathybook.club/css/bootstrap.min.css

we reference:

1
https://res.cloudinary.com/dev-empathy-book-club/raw/upload/css/bootstrap.min.css

(where css/ is a folder mapped to https://devempathybook.club/css/). Note that instead of image as before, we write raw to indicate that this should be considered an unknown file type and Cloudinary shouldn’t try to do any image processing with it.

Usually you’ll want to use a versioning strategy for your JS and CSS assets if you use a CDN, but the goal here was to be lazy on a static Jekyll site. Since there wasn’t much custom CSS and JS, I simply left a few files that are loaded directly from GitHub Pages, but things that won’t change frequently (or ever) are served via Cloudinary’s CDN. You can see the code here.

Cut Waste

You might notice, if you looked at the code from the last section, that a number of lines were commented out. It turns out that the Jekyll template I used bundled with it a number of JS/CSS frameworks and plugins I didn’t actually use. Removing them reduced the total page load size, and makes the page run faster, since there’s less for the CPU to worry about. As they say, no code is faster than no code!

The Outcome

I wouldn’t call the site blazing-fast now, but its PageSpeed mobile score went up from 30 to 50 in a few simple steps taking a couple hours total. There are more things to optimize, but these quick tricks helped bring down page load time a lot already. Importantly, time to first paint on mobile was cut by about 50%. That’s a much better experience for mobile users.

So go out, try these tips, and let me know in the comments how you did!

As a reminder, I work at Cloudinary, so if you do find anything here difficult to implement, I can pass along your concerns to the right people… 😉

P.S. If you use Jekyll or some other blogging framework, and you have many images on your site, it may be worth going further with automation using a plugin. For example, jekyll-cloudinary lets you define transition presets, and does all the work to generate URLs for images at various screen sizes. It’s pretty magical. Of course, if it’s a dynamic site, Cloudinary has a host of SDKs which can do everything discussed here, and much more!

NOTE: Cloudinary did not ask me to write this. Nothing in this post should be taken as representing anyone other than myself.

UPDATED 2019-01-29: Added option to include URL-encoded characters in a fetch URL.

Comments