How we (almost!) maxed out the Lighthouse score of our landing page - in one day

tim_nolet

Tim Nolet 👨🏻‍🚀

Posted on November 18, 2020

How we (almost!) maxed out the Lighthouse score of our landing page - in one day

We recently optimized the hell out of our Lighthouse score, and one of our landing pages went from a low 70s score to a cool 96 and above score. Below I describe what started as a quick lunch break peek into the Google Search Console - maybe some of it will help you optimize your own page!

Page Performance with Google Lighthouse

One of the most common tools to measure your page performance is Google Lighthouse. The advantage of Lighthouse is that you can run it against basically every public site out there. You can easily measure how others are doing and compare yourself.

Let's dive in:
Our Lighthouse performance score before any optimization.<br>

Houston, we have a problem! Some problems became very evident:

  1. Some key requests are blocking the page rendering for at least 550 ms.
  2. The network payload is pretty big, with more than 2.5 Mb of mostly images, CSS, and JS.
  3. We are delivering images as PNGs and might benefit from using a next-gen format.

Preload fonts & allow for swapping swap

We used four fonts on our landing page and figured that we have to improve the font handling because loading them blocked the browser for more than a second.

First, we made sure to preload the fonts by adding preload-statements to our HTML-head:

<link rel="preload" href="/fonts/fakt/FaktSoftPro-Normal.woff" as="font" as="font" type="font/woff2" crossorigin/>
<link rel="preload" href="/fonts/fakt/FaktSoftPro-Blond.woff" as="font" as="font" type="font/woff2" crossorigin/>
<link rel="preload" href="/fonts/fakt/FaktSoftPro-Medium.woff" as="font" as="font" type="font/woff2" crossorigin/>
<link rel="preload" href="/fonts/ionicons.ttf?v=2.0.1" as="font" as="font" type="font/ttf" crossorigin/>
Enter fullscreen mode Exit fullscreen mode

Please note that you need to include 'crossorigin' if you want to preload fonts.

Note: the crossorigin attribute indicates whether the resource should be fetched with a CORS request as the font may come from a different domain. Without this attribute, the preloaded font is ignored by the browser.

Find more here: https://web.dev/codelab-preload-web-fonts/

Second, we introduced a font-display: swap; to the font-face definition in our SCSS files. This enables the browser to use a fallback font and display the text before our custom fonts are loaded. The fonts get swapped afterwards. Essentially, this allows faster rendering and not getting blocked by font downloads.

@font-face {
  font-family: 'fakt-soft';
  src: url("../fonts/fakt/FaktSoftPro-Normal.eot") format('embedded-opentype'),
  url("../fonts/fakt/FaktSoftPro-Normal.woff") format("woff");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
Enter fullscreen mode Exit fullscreen mode

Image compression with IMGBot

Another significant bottleneck is the sheer size of the images we are using on our page.

Luckily we found a neat little tool that promised to optimize our images without losing quality: Imgbot. Too good to be true? No, it worked! Imgbot reduced our file size by 28% and some of the most used pictures by more than 50% just by applying a lossless encoding. See below..

imgbot optimisation

Image to WebP and the picture tag

Our next step was to implement WebP as one of the next-generation image formats. You can generate WebP pictures easily via Mac-Terminal. I just installed WebP via homebrew:

brew install webp
Enter fullscreen mode Exit fullscreen mode

Afterward, I used this command with a for-loop to convert all existing png-images to WebP. You can do the same with jpg images by replacing the 'png' with 'jpg' in the command below.

for i in *.png; do cwebp -q 90 $i -o "${i%.*}.webp"; done
Enter fullscreen mode Exit fullscreen mode

WebP is supported by Chromium and other major browsers but unfortunately not by Safari, but the HTML tag helps luckily to workaround this:

<picture>
   <source type="image/webp" srcset="/alternative/home-dashboard@2x.webp">
   <source type="image/png" srcset="/alternative/home-dashboard@2x.png">
   <img  class="alternativeto-hero-image img-fluid" src="/alternative/home-dashboard@2x.png" alt="checkly pingdom monitoring dashboard alternative"  />
</picture>
Enter fullscreen mode Exit fullscreen mode

Browsers knowing the picture tag will select the best image for them, and all other browsers will ignore the tag and work with the img-tag.

Converting the images to WebP decreased our already optimized image sizes by another fantastic 37%!

Lazy loading of images

Lazy loading means the image is only fetched when a user can actually see it in their browser. This will speed up the initial loading even further.

Browsers get smarter, and with that, browser-level lazy loading landed this year. Chromium-powered browsers and Firefox support it. The implementation for WebKit (Safari) is in progress. Read more here: Browser-level image lazy-loading for the web.

Chromium powered browsers run on at least 77% of all desktop computers. That led us to the decision to use <img loading=lazy> for most of the images. This will get interpreted by the browsers supporting this tag; all others will ignore it and act as before.

<img loading="lazy" class="rounded" src="/alternative/home-dashboard@2x.png" alt="Alternative to Pingdom" />
Enter fullscreen mode Exit fullscreen mode

Optimize CSS with purge and minify

A 950kb CSS file? Yes, that's what we had thanks to bootstrap, a lot of SCSS, and some other frameworks. Having a website, docs, and an API-documentation all relying on the same CSS file made it almost impossible to figure out what's needed and what isn't.

We used PurgeCSS, a tool to remove unused CSS. It can run in your deployment workflow. We utilize gulp to build and deploy our website code. Gulp-purgecss is an NPM module that integrates PurgeCSS as a build step in your pipeline by simply adding the following commands to the gulpfile.

import purgecss from 'gulp-purgecss'

....

//purgecss
gulp.task('purgecss', () => {
    return gulp.src('./public/css/**/*.css')
        .pipe(purgecss({
            content: ['./public/**/*.html']
        }))
        .pipe(gulp.dest('./public/css'))
})
Enter fullscreen mode Exit fullscreen mode

PurgeCSS was able to decrease our CSS files by more than 80%.

The next logical step was to apply 'minification,' another common optimization, by adding another step to our pipeline. Clean-CSS takes the well-formed CSS code and removes the spacing, indentation, newlines, and comments. These elements are not required by the browsers and use extra storage that needs to get transferred.

import cleanCSS from 'gulp-clean-css';

//minifycss
gulp.task('minify-css', () => {
  return gulp.src('./public/css/*.css')
    .pipe(cleanCSS({debug: true}, (details) => {
      console.log(`${details.name}: ${details.stats.originalSize}`);
      console.log(`${details.name}: ${details.stats.minifiedSize}`);
    }))
  .pipe(gulp.dest('./public/css'));
});
Enter fullscreen mode Exit fullscreen mode

Lazyload Intercom

The Intercom widget is another render-blocking resource. It's not very lightweight. We use Intercom for 99% percent of our customer support and want to continue to do so.

It turns out that there is a way to load Intercom lazily. We can do so by delaying the load of the Intercom-widget until there is a scroll event. We did so by amending the JS-snippet to this:

var intercomLoader = function(){
        (function(){var w=window;var ic=w.Intercom;if(typeof ic==="function"){ic('reattach_activator');ic('update',w.intercomSettings);}else{var d=document;var i=function(){i.c(arguments);};i.q=[];i.c=function(args){i.q.push(args);};w.Intercom=i;var l=function(){var s=d.createElement('script');s.type='text/javascript';s.async=true;s.src='your_widget_url';var x=d.getElementsByTagName('script')[0];x.parentNode.insertBefore(s, x);};if(document.readyState==='complete'){l();}else if(w.attachEvent){w.attachEvent('onload',l);}else{w.addEventListener('load',l,false);}}})();
          window.Intercom('boot', {
            app_id: 'app_id',
            custom_launcher_selector: '.open-intercom-link'
          })

            window.removeEventListener('scroll', intercomLoader)
      }
window.addEventListener('scroll', intercomLoader)
Enter fullscreen mode Exit fullscreen mode

Other optimizations

After diving into our site's mechanics, it became apparent that every third party snippet that we are including might negatively impact our page-performance. This led to an assessment of all third-party tools, and — no surprise — we were able to deactivate tools like Heap and Hotjar, which further improved the performance.

Result
lighthouse score

96! We have invested eight hours in improving our landing page's performance and, as a side effect, made https://www.checklyhq.com/ a lot faster. It's obvious to us that there are more improvements to make.

The next weeks will tell if the above will sky-rocket our Google-search ranking.

  1. https://web.dev/browser-level-image-lazy-loading/
  2. https://www.npmjs.com/package/gulp-purgecss

banner image: detail from "Louisbourg Lighthouse". Dennis Jarvis from Halifax, Canada, 2008. CC

This article originally appeared on blog.checklyhq.com

💖 💪 🙅 🚩
tim_nolet
Tim Nolet 👨🏻‍🚀

Posted on November 18, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related