Introduction
LightHouse 6 has been released mid May 2020. Available in command line with Google PageSpeed, Lighthouse 6 is embedded in Chrome Developer Tools since mid July 2020 (Chrome 84).
What’s new in version 6 ? Performance score is updated with 3 new metrics introduced while 3 other metrics are removed.
Performance metrics in version 5 :
Performance metrics in version 6 :
In version 6, deprecated metrics are :
- FMP (First Meaningful Paint) : these metrics were too variant.
- FCI (First CPU Idle) : not distinct enough from the metrics TTI (Time to Interactive).
- Max Potential First Input Delay : no longer shown in the report but still available in the JSON result file. It is now recommended to look at the metrics TBT (Total Blocking Time).
3 new metrics are introduced in version 6 and used when computing performance score :
- TBT (Total Blocking Time) : these metrics are not really new and were already available in Lighthouse 5, but they were not used when computing the score. TBT replaces FCI, FCI was used to measure when a page is ready for user input (clicks or taps), TBT measures how non-interactive a page is.
- LCP (Largest Contentful Paint) : a measurement of perceived loading experience more realistic. It marks the point during page load when the primary content has loaded and is visible to the user.
- CLS (Cumulative Layout Shift) : THE most interesting new metrics. It measures the visual stability. It quantifies how much a page’s content visually shifts around. Shifts (dynamic content insertion, images, fonts loads…) may generate a bad user experience.
With LCP and CLS metrics, Lighthouse 6 scoring is more realistic and close to user experience.
This paper focus mainly on the new CLS metrics : how to diagnose the unstable elements ?
Metrics weights - Thresholds
Weights have been also been updated in LightHouse 6. With the new computation rules, performance scores significantly decrease in version 6 compared to version 5 for a page. A tool is available online for comparing and defining objectives : Lighthouse scoring calculator.
The metrics TBT, LCP and CLS now account 55% of a page’s performance score in LightHouse 6 :
Metrics | Weight | Metrics | Weight |
---|---|---|---|
FCP - First Contentful Paint | 15% | TTI - Time To Interactive | 15% |
SI - Speed Index | 15% | TBT - Total Blocking Time | 25% |
LCP - Largest Contentful Paint | 25% | CLS - Cumulative Layout Shift | 5% |
In future releases, CLS metric weight in the score will probably be revised upwards. Actually it is only 5% as it is still a new somewhat experimental measure.
Quality scores thresholds per metrics are the following :
CLS - Cumulative Layout Shift
A bad user experience
The below video published on Web.dev website explains very well what CLS metric measures and how the shifts can cause real damage. A page is loading, the user can click or tap on elements, but unfortunately some content is dynamically added, buttons are then shifted : Oops instead of cancelling an order, the user clicks on the submit button.
Basically, CLS metric measures elements layout shifts that occur : DOM elements dynamically added above existing content, images or videos with unknown dimensions, fonts that render larger or smaller than its fallback, an ad or a widget that dynamically resizes itself…
Score recommendations
CLS measures the sum total of all individual layout shifts scores that occur unexpectedly in the viewport for the entire lifespan of the page. A good CLS score should be less than 0.1.
To calculate the layout shift score, the browser looks at the viewport size and the movement of unstable elements in the viewport between two rendered frames. The score is a product of two measures of that movement: the impact fraction and the distance fraction. For more informations about the score computation : Web.dev - Layout shift score.
Lighthouse results and CLS
In the Chrome Developer Tools Lighthouse report, the DOM elements that contribute most to the CLS of the page are displayed in the section "Avoid Large Layout Shifts" :
Using Lighthouse in command line, in the JSON result file, the CLS score is stored in the key ['lighthouseResult']['audits']['cumulative-layout-shift']
.
"cumulative-layout-shift": {
"id": "cumulative-layout-shift",
"title": "Cumulative Layout Shift",
"description": "Cumulative Layout Shift measures the movement of visible elements within the viewport.",
"score": 0.02,
"scoreDisplayMode": "numeric",
"displayValue": "1.569",
…
"numericValue": 1.5685546391150804
},
The DOM elements that contribute most to the CLS of the page are listed in the key ['lighthouseResult']['audits']['layout-shift-elements']
.
"layout-shift-elements": {
"id": "layout-shift-elements",
"title": "Avoid large layout shifts",
"description": "These DOM elements contribute most to the CLS of the page.",
"score": null,
"scoreDisplayMode": "informative",
"displayValue": "5 elements found",
"details": {
…
"items": [
{
"node": {
"snippet": "<div id=\"col-left\">",
"selector": "body > div#wrap > div#col-left",
"nodeLabel": "Sur le même sujet\nBases de données Time Series\nInfluxDB v2, prise en main. Prép…",
"path": "1,HTML,1,BODY,1,DIV,5,DIV",
"type": "node"
},
"score": 1.2030659115893447
},
{
"node": {
"snippet": "<nav class=\"topbar-nav menu\">",
"selector": "body > div#wrap > nav.topbar-nav",
"nodeLabel": "Sybase\nMS SQL\nOracle\nMySQL\nMariaDB\nPostgreSQL\nTime Series\nNoSQL\nUnix-Linux\nConc…",
"path": "1,HTML,1,BODY,1,DIV,3,NAV",
"type": "node"
},
"score": 0.341083267664828
},
{
"node": {
"snippet": "<div>",
"selector": "body > div#wrap > div",
"nodeLabel": "Home Article | InfluxDB - InfluxDB Server\n ",
"path": "1,HTML,1,BODY,1,DIV,4,DIV",
"type": "node"
},
"score": 0.02320975471193952
},
{
"node": {
"snippet": "<a href=\"/\" title=\"SQL Pour Administrateurs & Concepteurs\">",
"selector": "div#wrap > header > div.hleft > a",
"nodeLabel": "SQL Pour Administrateurs & Concepteurs",
"path": "1,HTML,1,BODY,1,DIV,0,HEADER,0,DIV,1,A",
"type": "node"
},
"score": 0.0004195429472025217
},
…
],
"type": "table"
}
},
Fine tracking CLS score with Google Chrome Developer Tools (Performance tab)
At the time of writing (end of August 2020), the feature described below is actually only available in Google Chrome Canary, it is not yet released in the official Chrome browser.
Google Chrome Canary is the development version and may be unstable but its installation is independent from an existing Google Chrome distribution.
Google Chrome Developer Tools (Performance tab) includes a new great feature to track layout shifts : layout shifts that occur are displayed in the new timeline "Experience". A score is computed for each layout shift, score added in the CLS metric.
In the Summary tab, for each layout shift, in addition to the score and the cumulative score, moves are detailed (locations, sizes) :
Hover the mouse pointer over the label "Moved from" and then the label "Moved to" to graphically visualize the layout shift of an element in the page :
In the summary, another useful information : "Had recent input". Layout shifts that occur within 500 milliseconds of user input have the hadRecentInput
flag set, they can be excluded from calculations.
Optimizing CLS score
Quite honestly, the CLS score thresholds set to 0.1 and 0.25 are a challenge, especially if content is added or removed dynamically using Javascript. Poor CLS scores are noticed in the following contexts :
- images without dimensions
- ads, embeds and iframes without dimensions
- dynamically injected content after page loading
- web fonts
- actions waiting for a network response before updating DOM
Focus on the elements contributing most to the CLS score of the page.
In the case study below, SQLPAC articles CLS scores are optimized from 1.2-0.9 to about 0.007 in few steps.
Measurements | Average CLS | Standard deviation | |
---|---|---|---|
Step 0 : Initial version | 100 |
0.904 |
0.170 |
… | … | … | … |
Step 4 : Intermediate step | 100 |
0.187 |
0.019 |
Step 5 : Final step | 100 |
0.007 |
0.000073 |
Measures are performed using a Python script and stored in a MySQL table. About this program : Measuring and storing pages performance metrics using Google PageSpeed Insights API and Python.
Average, Min, Max and standard deviations are computed using the SQL syntax below (Step 1 in the code sample) :
SELECT url, flag, count(*),
avg(cumulativeLayoutShift),
min(cumulativeLayoutShift),
max(cumulativeLayoutShift),
stddev_pop(cumulativeLayoutShift)
FROM `perf_pagespeed`
where flag='Step 1'
group by flag, url
order by date_measure desc
The initial HTML skeleton is the following :
<nav class="topbar-menu">
: menus container<ul class="metismenu">
: horizontal menu (one row)<div class="sitemap">
: sitemap<div id="col-right">
* : right column<section>
* : box in the right column<div class="js-toc">
* : table of contents
* : containers injected dynamically using Javascript
A drawing is more explicit :
height, min-height
When possible, apply a fixed size or a minimal size for containers in which some content is dynamically injected.
CLS score is drastically decreased setting a fixed height for the top menu and the sitemap (sizes defined empirically), as expected the standard deviation also drastically decreases. In the initial state, the standard deviation is near the score to reach (0.1) :
Measurements | Average CLS | Min CLS | Max CLS | Standard deviation | |
---|---|---|---|---|---|
Step 0 : Initial version | 100 |
0.904 |
0.789 |
1.319 |
0.170 |
Step 1 : Menus - Fixed heights
|
100 |
0.652 |
0.417 |
0.940 |
0.135 |
Step 2 : Sitemap - Fixed height
|
100 |
0.586 |
0.363 |
0.886 |
0.135 |
Great improvements, but the score is still bad following the Google criteria, further more the standard deviation is still greater than 0.1, threshold beyond which the CLS score is no longer considered good.
In the next 2 steps :
- The right column (
#col-right
) is no more dynamically inserted by Javascript, the empty container is added in the HTML skeleton and a minimum height is defined. - Minimum heights are defined for all boxes injected dynamically in the right column.
- Added in the HTML skeleton, the table of contents container (
js-toc
) is also no more dynamically inserted by Javascript. A minimum height is set depending on the number of elements in the table of contents.
Even empty, the greater the containers are predefined in the HTML skeleton with at least minimum heights (min-height
), the more the CLS score decreases significantly, as well as the standard deviation.
Measurements | Average CLS | Min CLS | Max CLS | Standard deviation | |
---|---|---|---|---|---|
Step 3 : Right column stabilization
|
100 |
0.325 |
0.316 |
0.427 |
0.027 |
Step 4 : Table of contents stabilization
|
100 |
0.187 |
0.008 |
0.255 |
0.019 |
Deferred CSS defining properties impacting CLS score (overflow…)
The score is now below 0.2, that’s better but not enough. A good score is below 0.1 ! In the previous steps, all elements that could be optimized have been fixed. So why is the score still too high and near 0.2 ?
It is very hard to find the root cause, the Pagespeed’s JSON results files do not give any clue, they only highlight the elements in the viewport for which a layout shift occurs.
Answer : be careful with the CSS property overflow
defined in a deferred CSS stylesheet.
In this case study, the property overflow: auto;
is applied to the pre
and code
containers in a deferred stylesheet, loaded after the DOMContentLoaded
event, so after the first painting. When page’s repaint occurs, the necessary scrollbars generate layout shifts on the elements in the viewport, even if the scrollbars are very far from the viewport.
All CSS properties that may generate layout shifts in the viewport (overflow
…) must be defined in the CSS critical path and not in a deferred way. Great, the score is now 0.007, very good score far away from the threshold 0.1 ! The standard deviation is 0.00007 : the page is stable and its stability is predictable and reliable.
Measurements | Average CLS | Min CLS | Max CLS | Standard deviation | |
---|---|---|---|---|---|
Step 5 : overflow property moved from a
deferred stylesheet to the critical main CSS stylesheet.
|
100 |
0.007 |
0.0067 |
0.0074 |
0.000073 |
Elements without dimensions
No surprise and no mystery, about images, videos, ads, iframes, etc., define the dimensions to avoid layout shifts, especially elements immediately visible in the viewport :
Measurements | Average CLS | Min CLS | Max CLS | Standard deviation | |
---|---|---|---|---|---|
First image in the viewport without dimensions
|
100 |
0.0201 |
0.0210 |
0.0288 |
0.0102 |
First image in the viewport with dimensions
|
100 |
0.0022 |
0.0015 |
0.0024 |
0.00013 |
LCP - Largest Contentful Paint
FMP has been found to produce inconsistent results and therefore is removed in the scores computations by LightHouse 6. LCP provides more accurate results and accounts for 25% of the performance score in LightHouse 6. LCP marks the point where the largest content element of a page loads and is visible, giving the user the impression a page has fully loaded. It is the best metric to measure website speed.
For once, Google has set reasonable and realistic thresholds. LCP is good when it is less than 2.5 seconds and considered as poor when greater than 4 seconds.
Just a quick note about finding the latest LCP element, knowing which element is the latest may be a useful indicator.
LCP is detailed in the performance tab (Chrome Developer tools) : timeline, latest element (type, size…).
The latest element LCP is also available in the Chrome Developer Tools Lighthouse report : section "Largest Contentful Paint element"
In the JSON result file using LightHouse in command line, the latest element LCP is in the key ['lighthouseResult']['audits']['largest-contentful-paint-element']
.
"largest-contentful-paint-element": {
"id": "largest-contentful-paint-element",
"title": "Largest Contentful Paint element",
"description": "This is the element that was identified as the Largest Contentful Paint. [Learn More](https://web.dev/…)",
"score": null,
"scoreDisplayMode": "informative",
"displayValue": "1 element found",
"details": {
…
"items": [
{
"node": {
"snippet": "<ins id=\"aswift_0_expand\" style=\"…\">",
"selector": "div#col-left > div#col-right > ins.adsbygoogle > ins#aswift_0_expand",
"nodeLabel": "ins",
"path": "1,HTML,1,BODY,1,DIV,5,DIV,0,DIV,0,INS,0,INS",
"type": "node"
}
}
]
}
},
CLS and LCP reporting in Google Search Console (webmaster console)
Since early August 2020, the bad and medium CLS and LCP scores are reported in Google Search Console (menu : Enhancements Core Web Vitals).
Once corrections are performed and then submitted to Google Search Console, the validation (or not) is notified approximately 20 days later.
In the results below : the evolution of CLS scores in Google Search Console with the corrections applied and described in the previous case study. The CLS scores were initially very poor and indicative of unstable pages.
Conclusion
LightHouse 6 now exposes the very interesting statistics CLS. If you have never taken care of a page’s stability before, like the author of this article, the CLS metrics let us discover new development best practices, knowing now how a bad user experience may have dramatic consequences due to a page’s unstability.
Depending on the code and the site’s complexity, in few steps pages can be stabilized :
- by predefining, when possible, the containers in the HTML skeleton, even empty, instead of dynamically inserting them using Javascript.
- by defining fixed or minimal sizes (
height
,min-height
,width
,min-width
) to the containers. - by setting the CSS properties impacting layout shifts (
overflow
…) in the main and critical CSS stylesheet, not in a deferred way. - by setting dimensions for images, videos, iframes, ads…
3 useful tools to optimize CLS scores :
- Chrome Developer Tools LightHouse tab : reports.
- Chrome Developer Tools Performance tab : Experience timeline.
- LightHouse JSON results files (command line).