How can we respond faster to user interactions?
First Contentful Paint (FCP) and Largest Contentful Paint (LCP) are both measures of the time required to render (paint) content on a web page. Although important, visual rendering metrics don’t capture the responsiveness of the page – in other words, how quickly it responds to user interaction.
What is First Input Delay?
First Input Delay (FID) is a Core Web Vitals metric that measures a user’s first impression of a site’s interactivity and responsiveness.
It indicates the time between when a user first interacts with a page and when the browser is actually able to respond to that interaction. FID is a Field metric (RUM) and cannot be simulated in a Lab environment (Synthetic Monitoring). Indeed, real user interaction is required to measure a response delay.
However, in March 2024, FID will be replaced by INP: Interaction to Next Paint. This new metric will make it possible to measure web page responsiveness more accurately. Unlike FID, INP will take into account all interactions that take place during navigation.
Read our article dedicated toInteraction to Next Paint, to better understand the differences between these two metrics and the reasons for their replacement within Core Web Vitals.
For a metric equivalent to FID with Synthetic Monitoring tools, you can look at Total Blocking Time (TBT). FID and TBT don’t measure exactly the same thing under the same conditions, but improvements in TBT generally improve FID.
The main cause of poor FID is heavy JavaScript execution. Optimizing the way JavaScript is parsed, compiled and executed on your web page will directly reduce FID.
First Input Delay: heavy JavaScript execution
The browser can’t respond to most user actions as long as it’s executing JavaScript on the main thread . In other words, the browser cannot react to user inputs when the mainthread is busy. To solve this problem and improve your performance, you can opt for one of the following techniques:
- Split Long Tasks
- Optimize your page so that it’s ready to respond to interactions
- Use a Web Worker
- Reduce JavaScript execution time
(NdT: we regularly feature techniques and tips to help you improve all your webperf metrics in our monthly newsletter, whichyou can subscribe to here).
Divide Long Tasks for better FID
If you’ve already tried to reduce the amount of JavaScript loading on a single page, it can be useful to divide Long Tasks into smaller, asynchronous tasks.
Long Tasks are periods of JavaScript execution during which users can find themselves faced with a frustratingly unresponsive interface. Any piece of code that blocks the main thread for 50 ms or more can be called a long task. In this situation, the loading and execution of the JS takes on too much importance in relation to the user’s immediate needs.
Splitting up long tasks can then reduce theInput Delay on your site for better responsiveness:
Chrome DevTools visualization of Long Tasks in Performance Panel
FID should improve significantly as you adopt best practices such as code-splitting and splitting your Long Tasks. Although TBT is not a Field measure , it can be used to assess the impact of techniques applied to optimize Time To Interactive (TTI) and FID. You can find out more about theeffect of Long Tasks on TTI.
Optimize your page so it’s ready to respond to interactions
JavaScript is often at the root of poor FID and TBT scores, so let’s see how in 3 common points.
- Executing proprietary (first-party)scripts can delay interaction readiness.
- Large JS sizes, long execution times and inefficient slicing can degrade a page’s ability to respond to userinputs , and therefore impact FID, TBT and TTI. Progressive loading of code and functionality can help improve responsiveness to interactions.
- Server-side rendered applications may appear to facilitate rapid display of the first pixels on the screen, but beware of user interactions being blocked by cumbersome script executions (e.g. rehydration to reconnect listeners). This can take several hundred milliseconds, sometimes even seconds, if route-basedcode-splitting is used. Consider moving more server-side computation, or generating more static content during page construction.
The image below shows TBT scores before and after optimizing the loading of proprietary scripts for an application. By removing the loading (and execution) of expensive scripts for a non-essential component from the critical path, users can interact with the page much sooner.
Data-fetching can have an impact on many aspects of interaction readiness
- Waiting for cascaded requests to finish loading (e.g. JS and data-fetching for components) can have an impact on interaction latency. Try to minimize dependencies when retrieving data from cascaded requests.
- Large volumes of inlineddata can reduce HTML parsing time and impact display and interaction metrics. Try to minimize the amount of client-side data to be processed.
Executing third-party scripts can also increase interaction latency.
- Many sites include third parties that can take up network space and make the main thread momentarily unavailable, which has an impact on interaction latency. To avoid this, you can opt for on-demand loading of third-party code (for example, don’t load ads below the waterline until they’re close to the viewport).
- In some cases, third-party scripts can pre-empt proprietary scripts in terms of priority and bandwidth on the mainthread , also delaying the moment when a page is ready for interaction. Prioritize loading so that users see what’s most valuable first.
Use a Web Worker to improve First Input Delay
A blocked main thread is one of the main causes of delays following an interaction. Web Workers enable JavaScript to be executed on a background thread . Moving non-UI operations to a separate thread helps to reduce the blocking time of the main thread, and therefore improve FID.
Consider using the following libraries to facilitate the use of Web Workers on your site:
- Comlink: a helper to abstract postMessage and make it easier to use
- Workway: help with Web Worker export
- Workerize: help for moving modules within a Web Worker
To take things a step further, here’s a guide to how Web Workers can execute code outside the mainthread .
Reducing JavaScript execution time for improved interactivity
Limiting the amount of JS on your page reduces the time the browser has to spend parsing, compiling and executing code. Your page can then respond more quickly to interactions.
To reduce the amount of JavaScript executed on your page :
- Defer unused JavaScript
- Reduce unusedpolyfills
Defer unused JavaScript code
By default, all JavaScript blocks rendering. When the browser encounters a script tag that refers to an external JS file, it must suspend what it’s doing and download, parse, compile and execute that script. As a result, it’s in your interest to load only the code you need for the page or to respond to user inputs .
To help you with this, the Coveragetab in Chrome DevTools shows the amount of unused JavaScript on your web page.
So, to reduce the amount of unused JavaScript on a :
- split your JSbundles ;
- defer all non-critical JavaScript, including third-party scripts, using async or defer.
Code-splitting involves breaking up a large JS bundle into smaller pieces that can be loaded conditionally (also known as lazy loading). Most recent browsers support dynamic import syntax for on-demand module retrieval:
[pastacode lang=”javascript” manual=”import(‘module.js’)%09%0A%20.then((module)%20%3D%3E%20%7B%09%0A%20%20%20%2F%2F%20Do%20something%20with%20the%20module.%09%0A%20%7D)%3B%09″ message=”” highlight=”” provider=”manual”/]
The dynamic import of JavaScript on certain user interactions (such as route modification or modal window display) then ensures that code not used for initial page loading will only be retrieved when needed.
In addition to general browser support, dynamic import syntax can be used in many differentbuildtools .
- If you use webpack, Rollup, or Parcel as a module bundler , take advantage of their dynamic import support.
- Client-side frameworks such as React, Angular and Vue provide abstractions to facilitate lazy loading at component level.
Here’s an article on the subject of code-splitting.
In addition to code-splitting, always use async or defer for scripts that are not required for the critical path or content above the waterline.
[pastacode lang=”javascript” manual=”%3Cscript%20defer%20src%3D%22%E2%80%A6%22%3E%3C%2Fscript%3E%09%0A%3Cscript%20async%20src%3D%22%E2%80%A6%22%3E%3C%2Fscript%3E%09″ message=”” highlight=”” provider=”manual”/]
Unless there is a specific reason not to, all third-party scripts must be loaded with defer or async by default.
Reduce unused polyfills
If you’re writing code using modern JavaScript syntax and targeting modern APIs, you’ll need to transpile it and include polyfills so that it works on older browsers.
One of the main performance problems with including polyfills and transpiled code is that new browsers shouldn’t have to download it if they don’t need to. To reduce the JavaScript size of your application, minimize unusedpolyfills as much as possible and limit their use to environments where they are needed.
To optimize the use of polyfill on your site :
- If you’re using Babel as a transpiler, use @babel/preset-env to include only polyfills for the browsers you’re targeting. For Babel 7.9, activate the bugfixesoption to exclude unnecessarypolyfills .
- Use the module/nomodule pattern to obtain two separate bundles (@babel/preset-env also supports this via target.esmodules).
[pastacode lang=”javascript” manual=”%3Cscript%20type%3D%22module%22%20src%3D%22modern.js%22%3E%3C%2Fscript%3E%09%0A%3Cscript%20nomodule%20src%3D%22legacy.js%22%20defer%3E%3C%2Fscript%3E%09″ message=”” highlight=”” provider=”manual”/]
Many of the newer ECMAScript features compiled with Babel are already supported in environments that manage JavaScript modules. So, by simplifying the process, you ensure that only transpiled code is used for the browsers that really need it.
You’ll find a guidehere for more details on techniques for serving the right code to recent browsers (in English) for faster pages.
Development tools for monitoring First Input Delay
Various tools are available for measuring and optimizing FID.
- Lighthouse does not include support for FID, as it is a Field measurement . However, Total Blocking Time (TBT) can be used to approximate it. Optimizations that improve TBT should also improve FID.
- Google’sChrome User Experience Report offers FID values derived from real-life conditions and aggregated for a given site.
Read also: