Developer, tech nerd and photographer

Shaving every kilobytes possible off your website

30 May 2020

So you’ve built your brand-new website: it’s neat, tidy and you’ve put all your heart and soul into it, but have you put any thought on its bandwith consumption?

Why would I care about its bandwith, doesn’t almost everybody has a high-speed Internet connection?

Well, let me explain why you should and give you a few tips on how to shave every kilobytes off your website’s bandwith!

Link to section Why you should care about bandwith in 2020

While you may be reading this blog at your desk with the comfort of your home’s fiber connection, you have to realize that nowadays more than half of Internet traffic comes from smartphones and possibly over a mobile network: 4G if you are in a dense area, 3G or even EDGE otherwise.

Then bandwith has a major implication on mobile users’ experience: your website may take ages to load because of its assets’ size, which in turn will frustrate your users!

Finally, if you serve your content through a paid CDN such as AWS CloudFront, reducing your website’s size will translate to cheaper bills.

Link to section My tips for reducing your website’s size

Note: I will often use Jekyll and AWS Cloudfront as examples in this article, but my tips apply to every website, static or not.

Link to section CSS stylesheets and Javascript

As they have the most significant impact on your website’s user experience, we will first take a look at CSS stylesheets and Javascript: until these assets are loaded, users won’t be able to interact with your website.

Link to section Load the right CSS rules and Javascript files for the right page

Most of the time, your website is composed of different type of pages: for example, this website is divided between its homepage, blog and photo gallery, each with their respective layouts and stylings.

Then, it makes sense to divide their respective stylesheets and scripts in different files that you add only to the pages that need them.

Using my website as an example, I have a main stylesheet containing Bootstrap and common CSS rules, and then I use Front Matter to specify if a page makes use of custom CSS stylesheets or some JS library:

<!-- _layouts/post.html -->
layout: default
extraCss: /assets/css/blog.css
<!-- _posts/2020-06-01-some-blog-post.md -->
layout: post
title: "Some blog post"
highlight: true
<!-- _layouts/default.html -->
    <link rel="stylesheet" href="/assets/css/main.css" />
    {% if page.extraCss %}
    <link rel="stylesheet" href="{{ page.extraCss }}" />
    {% endif %}
    {% if layout.extraCss %}
    <link rel="stylesheet" href="{{ layout.extraCss }}" />
    {% endif %}
    {% if page.highlight %}
    <link rel="stylesheet"
    {% endif %}
    {% if page.highlight %}
    <script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.18.1/highlight.min.js"></script>
    {% endif %}

For specific Javascript files such as the ones from the photo gallery, I simply add them at the end of the page’s <body>.

Link to section Single Page Applications: Use route-level code splitting

Speaking of the photo gallery, that leads me to Single Page Applications (SPAs).

Typically all of their code and CSS is bundled into a single fat JS file: this means that when users load a specific page of your application, they also download code for every other pages. Not very efficient, right?

This is why every frontend frameworks have introduced route-level code splitting: your app is split in modules that are automatically lazy-loaded when the user navigates to a specific route.

Please note that while it can dramatically cut down the initial download size, on smaller apps it can slightly increase launch time: the browser has to download the main module, initialize the app and then download the module corresponding to the current route.

Finally: unless you make use of preloading, it will also make first navigation from a module to another slightly slower as the browser will download the module when navigation is initiated.

Link to section Remove unused CSS rules using PurgeCSS

Nowadays, most of us build websites using CSS frameworks. Why would you do otherwise? They are great at getting started, makes your site look good with minimal effort and handle most use-cases!

But do you use all the features the framework offers? Certainly not, so why would you send all these unused CSS rules to your users, wasting so many precious kilobytes?

To remedy this situation, you can use PurgeCSS: this utility scans your HTML and CSS in order to remove CSS rules you do not use. There are even plugins for Jekyll and most popular build tools (PostCSS, Webpack, Gulp, etc.).

Small tip for jekyll-purgecss: the plugin will look for purgecss in ./node_modules/purgecss, so you must install it locally rather than globally (use npm i -D purgecss).

Link to section Minify your assets

Another way to further reduce your stylesheets’ and scripts’ size is minification:

Minification (also minimisation or minimization) is the process of removing all unnecessary characters from the source code of interpreted programming languages or markup languages without changing its functionality. These unnecessary characters usually include white space characters, new line characters, comments, and sometimes block delimiters, which are used to add readability to the code but are not required for it to execute. Minification reduces the size of the source code, making its transmission over a network (e.g. the Internet) more efficient. Wikipedia

There are different ways to minify you stylesheets and Javascript files: you can either use a minifier such as Minify or configure your build tools (such as Webpack or SASS) to do it for you.

For example, minifying your stylesheets in Jekyll is as simple as adding these two lines in your _config.yml:

  style: compressed

Link to section Pictures

After having seen how to reduce the size of your stylesheets and Javascript files, we will now take a look at pictures. They easily are the biggest consumer of bandwith: depending on resolution and compression, they can weight from a dozen kilobytes to tens of megabytes!

Link to section Serve the right picture size with responsive images

Rather than having every user download a high resolution picture, you can serve a picture in different sizes and let the browser pick the one that fits the user’s screen!

MDN has an excellent article on the subject but the gist is to use the srcset attribute to list every picture and size, like this:

<img srcset="picture-480.jpg 480w,
             picture-800.jpg 800w"
     alt="Look, a responsive picture">

Be aware that unless you specify the picture size in relation to the screen’s width using the sizes attribute, the browser will consider that the picture spans the entire width of the screen.

For example, here is how you would define sizes for pictures that span the entire width of Bootstrap’s container:

<img srcset="picture-1110.jpg 1110w,
             picture-930.jpg 930w,
             picture-690.jpg 690w,
             picture-510.jpg 510w,
     sizes="(min-width: 1200px) 1110px,
            (min-width: 992px) 930px,
            (min-width: 768px) 690px,
            (min-width: 576px) 510px,
            calc(100vw - 30px)"
     alt="Look, a responsive picture">

If you are using Jekyll, I recommend you use the incredibly useful jekyll-responsive-image along with this custom plugin I wrote: it will look for every picture referenced in Markdown documents and automatically resize and replace them with responsive images!

What about pictures used as backgrounds, you say? The same principle applies, but using media-queries this time:

@media (min-width: 1200px) {
  element {
    background: url('picture-1110.jpg');
@media (min-width: 992px) {
  element {
    background: url('picture-930.jpg');
/* etc. */

Link to section Make your pictures even lighter without sacrificing quality using WebP

In 2010 Google announced its WebP lossy and lossless image format based on its VP8 codec. Using WebP over JPEG results in 25 to 34% smaller files according to Google.

Except for Safari and Internet Explorer, WebP is supported by every major web browsers, which means that 34 of your users can benefit of lower pictures size!

The best thing about this is that you do not even need to use polyfills for browsers that do not support it: all you have to do is to use the <picture> element along with <source> elements to specify fallbacks.

  <!-- If WebP is supported, use it -->
  <source type="image/webp"
          srcset="picture-480.webp 480w,
                  picture-800.webp 800w">
  <!-- Otherwise, use JPEG -->
  <source type="image/jpeg"
          srcset="picture-480.jpg 480w,
                  picture-800.jpg 800w">
  <!-- Finally, if the browser does not support picture, fallback to img -->
  <img src="picture-default.jpg"
       alt="A responsive image using WebP when supported and falling back to JPEG when not">

Pro-tip: if you ever need to know in Javascript what picture is currently displayed in an img (whether it’s within a <picture> or it has a srcset attribute), it is stored in the currentSrc attribute! (MDN)

Unfortunately there is no such fallback mechanism for background images: you will have to use a feature detection library to know whether it is supported or not and adapt your background image accordingly. I suggest you read this CSS-Tricks article that covers the subject in great detail.

Link to section Lazily load pictures

Wait, there is a way to further reduce picture size other than resizing and more aggressive compression?

Well, not exactly: traditionally, web browsers would load every pictures contained in a page once it has parsed its content. In order to reduce the bandwith when the user first visits a page, you can alter the browser’s behavior in order to make it load pictures only when the user is about to see them: that’s called lazy-loading.

There are multiple ways to achieve lazy-loading:

Link to section Network and cache

Link to section Enable HTTP compression

Now that we have reduced our asset’s size, the only optimization left is HTTP compression: if both the browser and server/CDN support it, the latter will compress the data before sending it to the browser, resulting in reduced bandwith and faster downloads.

You can enable it either on your server (if you have one) or on your CDN. For example, this is just an option away in AWS CloudFront, and Apache and nginx have modules that enable HTTP compression.

Link to section Tell the browser to keep your assets in cache

My final recommandation will be to rely as much as possible on the browser’s cache: this will make subsequent visits on your website even faster as users won’t have to download your assets once again.

This is fairly simple to implement: all you have to do is set the Cache-Control header in your server’s or CDN’s response for static assets.

You can read this MDN article to find what value works best for you. If you use AWS CloudFront, you’ll only need to add a Lambda@Edge function on the folder(s) that contain your assets.

Link to section Going further

I hope those tips will help you make your website faster than ever!

If you want to find other areas for improvement, I recommend you use Google’s PageSpeed Insights tool: it will help you mesure your improvements and give you tips to make your website even faster.