Defer scripts until they are needed

Learn how to fix the Core Web Vitals by deferring scripts until they are needed

Arjen Karel Core Web Vitals Consultant
Arjen Karel

Defer scripts until they are needed

In this article I will show and explain a cool pattern to load scripts that are not needed during the start of the pageload at a later time, just before they are needed.

The absolute most effective thing you can do with JavaScript when it comes to the Core Web Vitals is to delay loading of a resource until it is needed. This will remove unused and unneeded JavaScript from the page and only load it when it is needed. This will fix the lighthouse warning 'reduce unused JavaScript' and also improve responsivity metrics like the interaction to Next Paint (INP).
We have been doing this with images for a long time. It is called lazy loading. With lazy loading a below-the-fold image is loaded right before it scrolls into view. This way we do not need to load the image immediately during page load and the browser can spend its precious resources on downloading, parsing and painting things that are actually needed.

Now imagine we could do the same thing with Scripts instead of images. Well it turns out that we can! Unfortunately it is not as simple as adding loading="lazy" to an image but with a bit of effort we can make it work

Step 1: Load scripts on demand

To add scripts to the page after page load we will need a small script that does this for us.

function injectScript(scriptUrl, callback) {
  var script = document.createElement("script");
  script.src = scriptUrl;
  if (typeof callback === "function") {
    script.onload = function () {

This function injects a script into the current web page by creating a new script element and appending it to the head of the document. The scriptUrl parameter specifies the URL of the script to be injected. The callback parameter is an optional function that will be called when the script has finished loading. When the script has finished loading, the onload event of the script element is triggered. If a callback function was provided, it will be called at this point.

Step 2: Load scripts on demand

The next step is to load scripts on demand. There are 2 common methods of doing this. The first is the more reliable 'when a part of the page is visible' and the second is the faster 'on interaction'.

2a: Intersection observer

The first method to load a script just before it is needed makes use of the intersection observer. The intersection observer is a reliable method that 'fires' when an element is intersecting with the visible part of the screen. We can use this behaviour to trigger a script download only when an element is visible. The downside of this method is that even though an element is 'on screen', it still might not be used.

function injectScriptOnIntersection(scriptUrl, elementSelector) {
  var observer = new IntersectionObserver(function(entries, observer) {
    entries.forEach(function(entry) {
      if (entry.isIntersecting) {

  var element = document.querySelector(elementSelector);

This function takes two parameters: scriptUrl is the URL of the script to be injected, and elementSelector is a CSS selector for the element that should trigger the injection.

The function creates a new IntersectionObserver object and passes it a callback function that will be called whenever an observed element intersects with the viewport. The callback function checks if the element is intersecting and, if so, injects the script and stops observing the element.

Note that the Intersection Observer API is not supported in all browsers, so you may need to use a polyfill if you need to support older browsers.

injectScriptOnIntersection('script.js', '#my-element');

This will inject the script when the element with ID my-element becomes visible in the viewport.

2b: On interaction

The most effective method to load JavaScript on demand is to load it when a visitor interacts with a certain element. For example a form. The advantage of using this method is that you will probably never load the script if it is not needed. The downside is that the download action is pretty late and we have to decide which events (mousover, hover, touchstart etc etc) we want to listen for.

function injectScriptOnInteraction(scriptUrl, elementSelector, eventTypes) {
  var element = document.querySelector(elementSelector);
  var eventHandler = function() {
    eventTypes.forEach(function(eventType) {
      element.removeEventListener(eventType, eventHandler);
  eventTypes.forEach(function(eventType) {
    element.addEventListener(eventType, eventHandler);

This function takes three parameters: scriptUrl is the URL of the script to be injected, elementSelector is a CSS selector for the element that should trigger the injection, and eventTypes is an array of event types that should trigger the injection (e.g. ["click", "mouseover"]).

The function finds the element using document.querySelector and adds event listeners to it for each of the specified event types. When any of the specified events occur, the injectScript function is called with the specified URL, and the event listeners are removed using element.removeEventListener.

  ['click', 'mouseover']

This will inject the script when the element with ID my-element is clicked or hovered over, and then remove the event listeners.


When scripts are not needed right away during the start op pageload it is a great idea to load them on demand! We can do this by using the intersection observer or on interaction. This will free up valuable resources during the early stages of pageload

I help teams pass the Core Web Vitals:

lighthouse 100 score

A slow website is likely to miss out on conversions and revenue. Nearly half of internet searchers don't wait three seconds for a page to load before going to another site. Ask yourself: "Is my site fast enough to convert visitors into customers?"

Defer scripts until they are neededCore Web Vitals Defer scripts until they are needed