Score a perfect 100 in Lighthouse audits — Part 1

a score that matters..

Lighthouse is an open source webpage benchmarking tool from Google Chrome team. It is available as a chrome extension as well as a node command line module. Once installed lighthouse can audit current page and ranks the page out of 100. A score above 80 is considered ‘Good’ where as anything less than that calls for further optimizations. Through this article I’m trying to explain how you can pass each individual test in lighthouse audit and get a perfect score of 💯!

Lighthouse categorize audits into four types

I. Progressive Web App (PWA)

II. Best Practices

III. Performance &

IV. Fancier Stuff

Since it’s going to be a rather long article I’ll split it into three parts. We’ll discuss PWA category in this post. Best practices will be covered in second part & performance along with fancier stuff will be dealt with in the third post.


I. Progressive Web App

Audits under this category check whether the webpage is fit to be considered as a progressive web app. PWA is a buzz word given to web pages which utilise some of the latest web technologies like manifest, service worker and Cache API to provide features like splash screen, instant loading, offline access etc. Once added to device home screen they behave more like an app than a website. Let’s see what are the criteria put forth by lighthouse for a progressive web app.

1. App can load on offline/flaky connections

The first audit basically checks whether the webpage has offline capabilities or not. To score in this section, you need to pass two checks.

✅ Register a service worker

Service worker is a script which runs in background separate from the page that can capture, manipulate and even respond to requests originating from your domain. It acts as a proxy between the browser and the server. To pass this test we need to register a service worker for page domain.

Put a javascript file named ‘service-worker.js’ at the root of your website and add the below code snippet to the HTML page. The code basically checks whether service worker is supported in browser & registers the service worker

<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
}
</script>

✅ Respond with a 200 when offline

Now that we have a registered service worker, let’s respond with a 200 even when there is no internet connection. For this, we need to intercept the requests originating from browser & return a ‘canned response’ incase there is no connectivity. Below snippet is for a really simple service worker which provides offline browsing support. If requested resource is available in cache it is served and for items which are not present in cache it responds with a custom 404 page.

let CACHE_NAME = 'sw-v1'
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll('./404.html'))
)
})
self.addEventListener('fetch', (event) => {
if (event.request.method === 'GET') {
event.respondWith(
caches.match(event.request)
.then((cached) => {
var networked = fetch(event.request)
.then((response) => {
let cacheCopy = response.clone()
caches.open(CACHE_NAME)
.then(cache => cache.put(event.request, cacheCopy))
return response;
})
.catch(() => caches.match(offlinePage));
return cached || networked;
})
)
}
return;
});

Above script is pretty basic and service worker is capable of doing way more that that. For more advanced service worker usages, head over to the below URLs.

https://github.com/GoogleChrome/sw-precache
https://serviceworke.rs/
https://googlechrome.github.io/samples/service-worker

These websites showcase service worker snippets for different usage scenarios and caching strategies, which is more that enough to get your started with. Choose one that suits your needs, copy it over to service-worker file and you will be up and running in no time.

⚠️ Gotchas!

  • Service worker is still not yet supported in all the major browsers
  • It is activated only on HTTPS enabled domains

2. Page load performance is fast

This section deals with the load performance of your website. There are four checks under this title.

✅ First meaningful paint: (target: 1,600ms)

It is the time taken by the page to get downloaded over the network, parsed and rendered in the browser for the very first time. To score 100 in this test, first meaningful paint should happen within 1.6 seconds.

Sadly there are no short cuts or snippets to get you through this step.

  • Keep total page weight under check
  • Avoid deeply nested DOM structure, which takes more time to parse
  • Use CDNs for static assets, to reduce network latency
  • Enable gzip compression for textual content on server
  • Compress images & video files
  • Only load assets which are absolutely necessary for initial render. Later we can enhance the site by loading more assets through AJAX
  • Load external fonts asynchronously.
  • Avoid external script tags in page header. Move such tags towards the bottom of page to prevent them from blocking page render. Alternatively add ‘async’ or ‘defer’ attribute to script tags, which will flag them as nonblocking resources.

Perceptual Speed Index: (target: 1,250)

This metric defines how quickly the contents of a page are visibly populated. It is the time taken for the website to appear visually complete. Lighthouse captures the screenshots of the webpage while it gets loaded and computes the speed index by analyzing the difference between first & last visual change from the screen shots. You will get a better score depending on how fast the page ‘looks’ complete.

The key to passing this audit is to minimize layout changes after initial page render. This gives user a sense of speed, while we are actually enhancing the page in the background.

  • Embed critical css for above the fold content into the page.
  • Do not alter page layout from Javascript files during page load.
  • Embedded fonts should have fallback fonts specified

✅ Estimated Input Latency: (target: 50ms)

This metric checks whether the page is responsive to user inputs. It follows the RAIL performance model put forth by Google. As per RAILS model the page should respond to user actions within 100ms.

In this audit instead of measuring whether the page responds to user actions within 100ms, Lighthouse checks whether the main thread is blocked for more than 50ms. If it is not, then ideally browser will not be able to respond to user inputs in less than 100ms. So how to pass this audit?

  • Do not block main thread for long durations.
  • Keep memory consumption under check. Frequent garbage collection will also stall main thread.
  • Throttle function calls which are getting repeatedly executed, mostly in case of animations. Use window.requestAnimationFrame() sequence ui updates
  • Offload any long-running computations to worker threads.
  • Low priority tasks should be performed only when browser main thread is idle. Make use of window.requestIdleCallback() to schedule such tasks.
  • Stick with CSS animations as far as possible and must modify only those css attributes which doesn’t trigger a relayout of the entire page. This limits animations to basically 3D transforms and opacity changes.

✅ Time To Interactive (alpha):(target: 5,000ms)

Time to interactive is the time taken by the page being loaded is considered usable and will respond to user inputs. It doesn’t require the page to be completely loaded, rather it is the time when the page is ready enough to start accepting user inputs. To pass this audit layout should have stabilised, web fonts loaded and render thread should be idle within 5 seconds.

Just like for above two audits, the key is to improve load performance and leave main thread open for accepting user inputs as early as possible in load phase. Less important scripts could wait for DOMContentLoaded event or PageLoad event for performing tasks.

3. Site is progressively enhanced

This check requires your website to get progressively enhanced on load. This is specifically applicable for Single Page Applications (SPA) where initial page render happens only after all the required resources are downloaded and parsed. All this time user will be staring at a blank page. As for lighthouse audit, there is only one metric that is measured under this category.

✅ Page contains some content when JavaScript is not available

Rather than serving just an app shell as the page content, serve some basic content within the page that will keep user engaged till dynamic page rendering takes over. For SPA’s try to pre render the page in advance and send the static content.

4. Network connection is secure

It’s 2017 and there is no excuse for not serving content over HTTPS, especially when your website is dealing with user data. HTTP content is sent unencrypted over the wire and prying eyes between server and user’s browser could easily decode information from the data stream.

To bring developers attention to this, browser like Chrome & Firefox has already started flagging http websites as insecure. Lighthouse checks for below metrics to make sure the page under test is secure.

✅ Uses HTTPS

This audit simply checks whether the site is being served from an https domain. So if website is not already in a secure domain, we’ll have to purchase a certificate for our domain from a Certificate Authority(CA). There are also free TLS providers like https://letsencrypt.org/ which provides certificates free of cost. So no more excuses.

✅ Redirects HTTP traffic to HTTPS

Any requests to insecure http domain of your website should get automatically forwarded to HTTPS domain. For example, if user tries to visit http://example.com, he should get automatically redirected to https://example.com instead. In developer terms, this is a permanent redirect for all incoming requests to port 80, to port 443. Below is an example of how we could achieve this on a node server. Apache also has similar configurations available.

const http = require('http')
http.createServer((req, res)=> {
res.writeHead(301, {
"Location": "https://" + req.headers['host'] + req.url
})
res.end()
}).listen(80)

5. User can be prompted to Add to Homescreen

Next set of audits checks whether the website could be added to home screen of user’s device. Once added to home screen, the web app will act like any other app on the device, with it’s own icon and runs in fullscreen without any browser elements to be seen. Also browser prompts the user to add your website to his homescreen on repeated visits to our website. Awesome! right? Let’s get all the test criteria right

✅ Registers a Service Worker

We did register a service worker in last step. So let’s move on

✅ Manifest exists

First create a ‘manifest.json’ with the below content at site root.

{
"name": "My Sample Website",
"short_name": "MyWeb",
"start_url": "/index.html",
"display": "standalone",
"background_color": "#FEFEFE",
"theme_color": "#333333",
"icons": [{
"src": "./assets/icons/img/icon-128x128.png",
"sizes": "128x128",
"type": "image/png"
}, {
"src": "./assets/icons/img/icon-152x152.png",
"sizes": "152x152",
"type": "image/png"
}, {
"src": "./assets/icons/img/icon-144x144.png",
"sizes": "144x144",
"type": "image/png"
}, {
"src": "./assets/icons/img/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
}]
}

Now attach it to the website by adding a link tag to the manifest file.

<link rel="manifest" href="manifest.json">

✅ Manifest contains start_url

Note that we have an attribute ‘start_url’ in the manifest file which denotes the start page for the web app. Every time user launches the webpage this start url will serve as the first page. Ideally it should be the landing page of your website.

✅ Manifest contains icons at least 144px

Every app on user’s device have an icon to uniquely identify the same. So does our website. Note the ‘icons’ attribute in manifest. It is an array of images which will serve as your site’s icon on different device resolutions. Have an icon of at least 144px in the manifest and we will pass this test as well.

✅ Manifest contains short_name

The manifest must also contain the ‘name’ and ‘short_name’ attributes. While name field depicts the complete name of your website, short_name will be used when the entire name cannot be displayed due to space limitations, like on homescreen, app drawer etc. So pick a short name for the website which will uniquely identify your website among other apps.

6. Installed web app will launch with custom splash screen

Browsers like Chrome also displays a splash screen for a web app when launched from home screen. This splash screen is auto-generated from the information that is given in manifest file. Below checks makes sure that an appropriate splash screen will get generated for your website

✅ Manifest exists
✅ Manifest contains name
✅ Manifest contains icons at least 192px

We’ve already handled the above checks in previous section.

✅ Manifest contains background_color

Add ‘background_color’ attribute to manifest.json with a color value which will serve as the background color for your web app till the stylesheet gets loaded. The same color is used for generating splash image background as well so that there is a nice transition from splash to actual page.

✅ Manifest contains theme_color

The ‘theme_color’ attribute will be used to theme browser elements and app switcher frame for the website. So make sure that we have a theme color also defined along with the background color.

7. Address bar matches brand colors

This test makes sure that the browser addressed bar will get themed as per the site theme when a user navigates to our website.

✅ Manifest exists
✅ Manifest contains theme_color

We’ve already added a manifest and it has theme_color added. So when launched from homescreen the theme color will be used to style the browser and OS elements for our web app.

✅ Has a <meta name=”theme-color”>

Theme color for a page can also be defined in the webpage itself through the ‘theme-color’ meta tag. So if a user is navigating to our website from browser itself we still have the theme applied. Add the below meta tag with the same color that we’ve defined in manifest.

<meta name=”theme-color” content=”#333333">

8. Design is mobile-friendly

The last audit in this category checks whether the webpage is mobile friendly or not. It has two checks for the same

✅ Has a <meta name=”viewport”> tag with width or initial-scale

Checks whether your page has view port meta tag added to the header. Viewport meta tag controls how the page is displayed on a mobile device. Setting the content width of page equal to that of device with and matching CSS pixels with available device pixels will make sure that content gets properly scaled for device viewport irrespective of its dimensions and orientation. Add below meta tag to page header to pass this audit.

<meta name=viewport content="width=device-width, initial-scale=1"/>

✅ Content is sized correctly for the viewport

This audit checks whether width of the website content matches to available viewport size. To pass this audit make sure that the website is responsive and the elements will resize itself to the available viewport size. Use percentage widths for the layout elements and media queries to alter the layout for different device form factors.


That concludes the first audit category of Lighthouse. Hope you’ve got everything covered till this point. If you have some questions or suggestions on how to make this article better, do let me know through the comments section below. Also don’t forget to follow me so that you wont miss the next two parts of this article.