At FutureLab, we understand the importance of performance in delivering a great user experience and ensuring that content is optimized for search engine rankings. As the web and application development has evolved, feature rich content can be problematic and slow to load, particularly on mobile devices. It is more important than ever to pay careful consideration towards performance when designing and engineering solutions.
There are multiple factors to take into account when considering performance on a project. Third party embeds or scripts can often have a negative impact on performance, but in some cases the inherent value that they bring may justify the performance impact incurred.
While performance is always important, actual needs vary by website. For example, an internal company tool might not place as much importance on it as an online store. As such, FutureLab has developed these baseline performance best practices to be implemented on all our projects. On some projects more will be done for performance, but we strive to achieve this baseline no matter what. Also worth mentioning is we don’t have any sort of standard “numbers” e.g. core web vitals, PSI score, or TTFB. This is because there is no one number that can apply to all websites.
Caching is a key aspect in reaching optimal performance both from a server and browser optimization perspective, below are caching approaches:
unicode-range to subset fonts if they are being served locally or through Google Fonts.As part of design reviews, engineering teams should provide feedback on the following:
Read more on the FutureLab systems performance best practices.
Note: When starting a new project or inheriting an existing one, take before screenshots of the Google PSI report so performance can be compared before-and-after.
Web Vitals, a performance initiative by Google, provides us a set of rules, concepts and metrics in order to serve users with the best web experience possible. Performance measuring in the past has often landed in the domain of engineers. However with the introduction of Web Vitals, site owners can now gain an understanding of the performance impacts and shortcomings of their sites without a deep understanding of web technologies. Web Vitals aim to simplify understanding and provide pertinent guidance to site owners and engineers alike in order to optimize user experience.
At FutureLab, we closely monitor Core Web Vitals (a subset of Web Vitals) during development which was introduced in June 2021 into Google’s ranking algorithm. Ensuring healthy Web Vitals throughout the build and/or maintenance is of paramount importance and requires a shift not only in how we go about building components, but in maintaining a high level of quality across overall user experience.
At FutureLab we acknowledge that achieving healthy Web Vitals across the board is not siloed to one discipline. Ensuring healthy Web Vitals requires a cross discipline approach spanning Front-end Engineering, Web Engineering, Systems, Audience and Revenue and Visual Design.
As defined by Google, the 3 Core Web Vitals are currently:
Largest Contentful Paint is an important metric for measuring perceived user performance, specifically loading performance. This metric reports the render time of the largest element on the page that is visible to the user.
An LCP score of 2.5 seconds or less is considered to be a conducive measurement for good user experience.
LCP is measured in seconds (s) and can be tracked against the following DOM elements:
<img><image> - inside an SVG<video>background-imagedisplay: block)The quickest way to diagnose the Largest Contentful Paint element on the page is by following these steps:
⌘ ⇧ E shortcutOnce you have diagnosed which element on the page has the Largest Contentful Paint, the next step is to figure out why. There are 3 main factors that contribute to LCP:
It’s important that your server is optimized in a way that doesn’t have a domino effect on other vitals. To measure the “speed” of your server you can track the Time to First Byte (TTFB) vital.
Here are some high-level guidelines for ensuring Largest Contentful Paint occurs as fast as possible:
<link rel="preconnect"> and <link rel="dns-prefetch"> for assets that originate at third-party domains.The time it takes the browser to fetch resources like images or videos can also have an effect on LCP:
preload the image resource ahead of time. For responsive images you will need to add the imagesrcset and imagesizes attributes: <link rel="preload" as="image" imagesrcset=" image-400.jpg 400w, image-800.jpg 800w, image-1600.jpg 1600w" imagesizes="100vw" />.Cumulative Layout Shift measures the visual stability of a web page. CLS can be an elusive metric to get right as elements targeted as having a layout shift are often not the root cause. By ensuring limited layout shifts on the page, visitors will be presented with a smooth and delightful user experience.
A CLS score of 0.1 or less is considered to be a conducive measurement for good user experience.
It’s important to understand that the CLS metric does not just measure one offending element. The CLS score reported is the sum total of all layout shifts on the page. A layout shift occurs any time a visible element (i.e above the fold), changes its position from one rendered frame to the next.
To be clear, a layout shift is only considered a problem if it’s unexpected - so a shift in an elements position that was triggered on purpose by a user is acceptable.
It’s useful to know that a layout shift can be caused by the following events:
Considering the above, it would be plausible that nearby DOM elements could then change their position and dimensions based on another elements movement.
The quickest way to diagnose an element that has undergone a layout shift is by following these steps:
⌘ ⇧ E shortcutAs an alternative, you can also diagnose Layout Shifts on the page by:
⌘ ⇧ P to open the actions console.Elements that cause CLS can be easily fixed in some instances. As a general rule of thumb ensure that:
width and a height attribute. This is because HTML gets parsed before CSS and the browser will reserve space if it knows the dimensions and aspect-ratio of the image.width and height attribute.transform properties rather than box-model properties to prevent reflow and layout changes in the browsers Critical Rendering PathWhen it comes to ads, it’s important that slot sizes are consistent in order to prevent CLS. Ads are generally difficult to predict considering that Ad Servers can serve ads at different heights and widths depending on impression data. There are a number of ways to mitigate this:
min-height CSS property: <div id="ad-slot" style="min-height: 250px;"></div>GPT.js script has ad data you can use the following function:googletag.pubads().addEventListener('slotRenderEnded', function(event) {
var size = event.size;
if(size === null) return;
var slot = event.slot;
var slotDiv = document.getElementById(slot.getSlotElementId());
if (size[0] > slotDiv.clientWidth) {
slotDiv.style.width = size[0] + 'px';
}
if (size[1] > slotDiv.clientHeight) {
slotDiv.style.height = size[1] + 'px';
}
});
googletag.pubads().collapseEmptyDivs(); to ensure that ad slots that probably won’t fill take up no height and width on the page.Handling FOUT (Flash of Unstyled Text) and FOIT (Flash of Invisible Text) have become a much discussed topic recently. It’s important to be aware that your page could subscribe to either of the unwanted side-effects of embedding custom fonts. Here’s what you can do to mitigate those effects:
font-display: swap if your fonts are hosted locally.<link rel="preload"/> schema in conjunction with font-display: optionalFirst Input Delay measures the interactivity of the web page. It quantifies the user’s experience with regards to how fast the page load feels. By maintaining a low FID score, users will feel like the page is loading faster.
First Input Delay is specifically purposed for measuring how quickly the page becomes interactive to the user on their first impression, where as a vital such as First Contentful Paint measures how quickly the page becomes visible. These are 2 important concepts to grasp when it comes to debugging and diagnosing Core Web Vitals.
An FID score of 100 milliseconds (ms) or less is considering to be a conducive measurement for good user experience.
FID is a unique Core Web Vital and is not actually tracked in Lighthouse or other service metrics. FID is a field metric, meaning that its score is generated by collating data from millions of websites accessed by Google Chrome users. When it comes to generating a score for FID “in the lab” or otherwise through Lighthouse, you will be looking to improve the Total Blocking Time (TBT) metric. You can think of TBT as a proxy to FID. This is because FID requires a real user and real users cannot be “spoofed” by Lighthouse.
Most importantly, this vital measures the delay from when an event has been received to when the main thread of the browser is idle, this is also known as “input latency”. The “event” can include user events like clicks or taps, but there are far more events in JavaScript that do not require actual user input. FID does not measure the time it takes to actually process the event in JavaScript or the time that it takes to update the UI based on event handlers.
Unfortunately, diagnosing Total Blocking Time in Chrome is not as easy as diagnosing Largest Contentful Paint or Cumulative Layout Shift. One of the biggest clues for diagnosing TBT is identifying heavy JavaScript execution on the main thread. This requires an understanding of how the browser parses HTML and JavaScript as well as what is known as a Long Task. A Long Task is any JavaScript-based task on the main thread that takes longer than 50 milliseconds (ms) to execute. While the browser is executing a JS task on the main thread, it cannot respond to any user input, as JavaScript is not a multi-threaded language.
You can identify any Long Tasks on your webpage by following these steps:
⌘ ⇧ E shortcutUsing Google Chrome’s “Coverage” tab can provide critical insight into how much of the JavaScript on the page is actually being used. Identifying this code can help you off-load non-critical JavaScript until after page load.
FID can be fixed in a number of ways that relate to analyzing JavaScript performance:
We’ve already discussed at a high-level what Long Tasks are, but more importantly, we want to make sure that these long tasks can be broken up into smaller, asynchronous tasks. This can be achieved by code-splitting your JavaScript bundles using ES6 Dynamic Import syntax as well as keeping an eye on polyfill’s clogging up your main bundle. Fallback polyfills can add a tremendous amount of bloat to bundle sizes.
Other factors to look at are the size of your JavaScript bundles as well as how many JavaScript files your page is loading during initial load. If the main thread has to parse unnecessary JavaScript that may not be needed by the user the chance of Long Tasks preventing user interaction during load rises exponentially. Where possible you should also defer the fetching of data from API services as network latency can also effect TBT.
One area of conversation (especially with clients) should be around the number and purpose of 3rd-party scripts. Multiple 3rd-party scripts can quickly become unmaintainable and lead to delayed JavaScript execution. Ensuring 3rd party scripts use the async and defer attributes can also help improve latency.
A plethora of tools have become available to manage and maintain healthy Core Web Vitals. In fact, most performance/reporting based tools now offer some kind of Web Vitals-based data. At FutureLab, we use the following tools to report accurate Web Vital metrics:
This library can be used during development to diagnose Web Vital metrics. It comes with a fairly easy to understand API that directly tracks Web Vital data on page load. The data can be a bit hard to read but if you’re looking for a programmatic approach to understanding the health of your web page, this library won’t let you down.
import {getLCP, getFID, getCLS} from 'web-vitals';
getCLS(console.log);
getFID(console.log);
getLCP(console.log);
You can also use this library if you would like to send site data on Web Vitals directly to your analytics service. The package won’t help you identify what’s wrong, but it will tell you where your site is starting to slip in terms of web vitals health in a customized and dynamic way.
import {getCLS, getFID, getLCP} from 'web-vitals';
function sendToAnalytics(metric) {
const body = JSON.stringify({[metric.name]: metric.value});
// Use `navigator.sendBeacon()` if available, falling back to `fetch()`.
(navigator.sendBeacon && navigator.sendBeacon('/analytics', body)) ||
fetch('/analytics', {body, method: 'POST', keepalive: true});
}
getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getLCP(sendToAnalytics);
Lighthouse, which is built into Google Chrome’s DevTools, is a significant tool for tracking performance data on an ad-hoc basis. Whereas the tool is accurate and performs many important audits, it should only be used as a “check up” on performance data and not a real assumption of how multiple users perceive performance on the site.
The Lighthouse CLI tool can also be used with more efficiency if you are managing multiple URL sets. To install the CLI:
npm install -g lighthouse
then run the CLI against a URL:
lighthouse <url> --preset=desktop --view
WebPageTest is the preferred tool at FutureLab for compiling performance budgets and identifying areas of improvement. We run tests with the following settings:
These settings give us a median test range to see accurate results. An incredibly useful feature of WebPageTest is their Chrome Field Performance section which reports on where your site lands up based on the 75th percentile of all websites that Chrome tracks in the Chrome UX Report (CrUX). This is a really important contextful feature that should not be ignored.
Addy Osmani has written a really useful and compact extension that reports on the 3 Core Web Vitals.