Optimizing my site for performance

Alex Bennett 12 May 2018

Web performance, something all developers know about but I’m quite sure something we all sometimes overlook. It’s all well and good building an amazing site that works on our machines and looks ‘ok’ on mobile, but when it comes to crunch the numbers in lighthouse, many sites perform pretty badly.

In an effort to enforce a new performance culture at Epiphany when building sites I thought I’d play around trying to optimize my own site this weekend. The results proved fruitful increasing my site performance in lighthouse across all audits.

These were the initial results, bearing in mind my site is not heavy - there isn’t much javascript to speak of, I always optimize images and the styles are lightweight. However, clearly some improvements can be made here.

Initial results - no performance tweaks

Why Optimize?

We want users to interact meaningfully with what we build for the web. If it’s a blog, we want people to read posts. If it’s an online store, we want to turn prospective shoppers into buyers. If it’s a social networking web app, we want visitors to write posts, upload photos, and interact with one other. Jeremy Wagner - Google

A fast site is obviously better for user experience, before a site loads users have to wait for the browser to download, parse and render the site. Until this happens there is no user experience to speak of. The lack of intractability is fleeting on fast connections and optimized sites but it’s noticeable and damaging to the user’s experience on slow connections. When the page does start rendering CSS, javascript and other resources like images they each present their own unique performance problems.

The BBC found they lost an additional 10% of users for every additional second their site took to load.

Back in the day performance was more of an afterthought - we could ensure things were minified and change a few server configs and that was job done. Now with the growing complexity of sites, it should be a process baked into development with continual monitoring, reporting, and refinements.

Some metrics we can use to check if our site is performant are;

  • First Meaningful Paint (When content first appears on the page)
  • Time to Interactive (The point at which layout has stabilized, the time when a user can interact with the UI)
  • Overall Weight (Page size in bytes)
  • Overall Asset Count (Total number of resources the browser has to load)
  • Third-Party Domains (Total number of external requests)

My website

The site is a simple perch cms build with few assets in terms of icons, however, it does have quite a few images loaded via the cms. It has a minified stylesheet and bundled javascript as well as modern loaded into the page. I kept the styles lightweight as its a minimal design so the CSS when minified comes in at 91kb and the javaScript comes in at 237kb, the total page weight on the homepage is 1.7mb, pretty lightweight but this is delivered to the browser when uncached and without any modern niceties like gzip compression or a service worker. I steer clear of large bloated frameworks like bootstrap for styling so everything that’s on page whilst it may be shared across the site (rather than split for per page CSS) is specific to the site. I did, however, use jQuery for the sites javaScript which adds some unnecessary bloat. Since I built this site I have stopped using jQuery in most of my development, given the modern web, API can do everything well. The script file is still lightweight when bundled so, for now, I have left jQuery in.

Optimizing quick wins

A few things I quickly did involved reordering how things load in on the page, moving modernizr to the footer above bundle.js and also moving the stylesheet into the footer. This removed render blocking styles and scripts from stopping the page loading, however, it meant the site loads blank for a split second so I added some critical CSS or above the fold styles directly into the head, minified of course. My critical css gave the header and menu a basic style that won’t conflict with the rest of the page when the stylesheet eventually loads and styles the banner section below the header - this is what the user will first see when they land on the page, anything below this is irrelevant for critical (above the fold) styles.

Lazy Loading images

Given there are a number of images on the site it made sense to introduce a lazy loader, this staggers the loading of images to reduce the initial page weight and time to first paint. In chrome developer tools network tab you can see the site prior to lazy loader pulling in all the resources on the homepage.

Initial page load without lazy load

There are a number of resources for the browser to deal with on initial load, what I want is a seamless experience for the user but to reduce this initial page load. I decided to go with LazySizes for my loader and a plugin bgSet given most of my images are loaded in as background images. The implementation of it was easy given no JS configuration is needed, I simply imported it into my bundle and added the lazyload class to all images with a data-bgset attribute, to ensure the user gets something on load and not just blank space I added a lightweight placeholder image.

import lazysizes from ‘lazysizes’; import ‘lazysizes/plugins/bgset/ls.bgset.min’;

<a href=”<perch:content id=”url” type=”text” />” class=”item item–work lazyload” data-bgset=”<perch:content id=”image” type=”image” label=”Image” width=”640” />” data-sizes=”auto” style=”background-image:url(‘/img/placeholder.jpg’);” target=”_blank”> / Internal HTML / </a>

After adding lazy load the network tab shows that most image resources are deferred, again this reduces the initial page load and as such the time to first byte. This is another quick win that can be rolled out across the whole site saving much of the initial weight of the page on load. The way LazySizes works only reveals the image when a user scrolls them into the viewport, as such this prevents the browser downloading all the resources initially, using the intersection observer and a fallback for legacy browsers.

After load and on user interaction the images are loaded in

My performance score has greatly improved with these minor changes but there is still more to be done. The next steps are adding progressive web app technologies, mainly service worker to make use of its caching abilities. At this point, I’ve also started editing various parts of the site to improve various styles and functionality. I noticed for example that my blog post carousel was stuck pulling in blog posts starting from the first rather than from the latest.

After lazy load has been added

Service Worker

Service workers essentially act as proxy servers that sit between web applications, the browser, and the network (when available). They are intended, among other things, to enable the creation of effective offline experiences, intercept network requests and take appropriate action based on whether the network is available, and update assets residing on the server. They will also allow access to push notifications and background sync APIs.

Initially, all I want to use the service worker for is to cache my static resources like images, styles and javaScript. I could use Googles Workbox for this but it seems a little overkill. Instead, I opted to include a service worker generator into my existing build process grunt-service-worker.

Due to how my grunt task manager is setup using grunt-load-config I created a JSON file for my service worker plugin settings inside of which, I define what I want to cache and the base directory the script should look in, not only to find the assets but also generator the service worker code. I then include my service worker via a script tag on the page which registers the service worker if the browser is compatible.

For more information about service workers see the resources section at the bottom of this post.

{     ”dist”: {         ”options”: {             ”baseDir”: “./”,             ”workerFile”: “sw.js”,             ”staticFileGlobs”: [                 ”js/.js”,                 ”css/.css”,                 ”fonts/*/.ttf”,                 ”img/*.{gif,jpg,png,svg}”             ]         }     } }

<script> System[‘import’].call(System, ‘app’);

        if ('serviceWorker' in navigator) {
          navigator.serviceWorker.register('/sw.js', {scope: '/sw/'})
          .then(function(reg) {
            // registration worked
            console.log('Registration succeeded. Scope is ' + reg.scope);
          }).catch(function(error) {
            // registration failed
            console.log('Registration failed with ' + error);
          });
        }

</script>

Due to the nature of service workers they only work across secure https connections, when developing locally this is a pain to setup and not necessary so they also work when accessed via localhost, unfortunately, I develop using virtual names, so alex-bennett.local wouldn’t register my worker, to test I set the Allow invalid certificates for resources loaded from localhost flag in chrome flags to get around this.

At this point, my site is performing very well on speed tests but there are a few amends I could still do to speed things up, the main being enable gzip compression server side. My site is hosted with cpanel so configuring it to compress text & php is quite easy. All I needed to do was login and enable Compress All Content in the website optimization settings.

Blazingly fast website performance, even faster after the initial load thanks to service worker!

So goal achieved in terms of speed but lighthouse has flagged some things I could still be doing, I can further compress images and optimize them for specific sizes, possibly throw in some responsive images or let lazy sizes handle the correct image. However, I could add some more progressive web application functionality and improve a few accessibilities. My site doesn’t really need to work ‘offline’ but adding a manifest and splash screen to the project aren’t much hassle.

Manifest

The web app manifest is a simple JSON file that gives you, the developer, the ability to control how your app appears to the user in areas where they would expect to see apps

I don’t specifically need the functionality a web manifest offers, but as a test, I’ve added it anyway. Adding manifest content is very easy, it’s simply a json file in the root of the site linked in the HEAD with defined content specific to your project, below you can see the manifest for this site.

{ “short_name”: “AB Developer”, “name”: “Alex Bennett | Frontend Developer”, “background_color”: “#6dcff6”, “theme_color”: “#6dcff6”, “display”: “standalone”, “orientation”: “landscape”, “icons”: [{ “src”: “img/touch/icon-128x128.png”, “type”: “image/png”, “sizes”: “128x128” }, { “src”: “img/touch/apple-touch-icon.png”, “type”: “image/png”, “sizes”: “152x152” }, { “src”: “img/touch/ms-touch-icon-144x144-precomposed.png”, “type”: “image/png”, “sizes”: “144x144” }, { “src”: “img/touch/chrome-touch-icon-192x192.png”, “type”: “image/png”, “sizes”: “192x192” }, { “src”: “img/touch/splash-512x512.png”, “type”: “image/png”, “sizes”: “512x512” }], “start_url”: “/” }

As a result, I have now improved scores across the board (bar accessibility which is complaining my fonts are too small for the partially sighted). My time to first paint has gone from 3,710ms to 1,540ms on a fresh load without cache support and a lightning fast 321ms with cached assets.

If you want to have a go at some of these practices yourself and track your progress you can download and view previous reports using the Lighthouse Report Viewer.

Progressive web application tooling added!

So in this optimization, I have covered how to reorganize site files for optimal performance and add tools to improve how the site is rendered to reduce the initial load for the user. I have added a service worker and progressive web application tooling and along the way refactored various parts of my blog and metadata ultimately improving the sites SEO visibility. I have learned a lot doing this and hope to now incorporate these lessons into the workflow at epiphany, ensuring we deliver optimal performing sites for our clients which will ultimately yield better results for their users and improve conversions! If you want to test your site against a benchmark, Google offers a nice visual tool for the job - Is your page mobile friendly?. The results of my site show the work I’ve done was well worth the effort!

Mobile Friendly Industry Comparison

Resources

Contact

If you have an interesting opportunity or a project in need of a developer, please get in touch: