Skip to main content

How To Defer JavaScript On Your Website

In this article, we will explore how deferring JavaScript can improve page load performance. We’ll look at why delaying script execution helps prevent render-blocking issues, how to determine which scripts to defer, and look at how deferring scripts can improve Core Web Vitals.

Why defer JavaScript loading?

Deferring JavaScript loading improves performance by allowing the HTML document to be parsed and rendered before executing scripts. This prevents makes these scripts not render-blocking, reducing page load times and improving user experience.

In this request waterfall we can see a render-blocking JavaScript request. Deferring this script should help improve the First Contentful Paint (FCP) score.

Request waterfall with render-blocking script

After running an experiment to defer the script, the FCP score improves by 295 ms as the script now no longer blocks rendering, providing a faster experience for the user.

Deferred JavaScript allowing page to render sooner

How to defer JavaScript loading

Add the defer attribute to your script tag to allow the script to load alongside HTML parsing while ensuring the script executes only after the document is fully parsed.

This method ensures your script runs once the DOM is ready, while preserving the execution order if multiple deferred scripts are used.

<script type="text/javascript" src="chat-widget.js" defer />
tip

You can also use the async attribute to make scripts not render-blocking, but async does not maintain the execution order or ensure the DOM is ready when the script runs.

What scripts should be deferred?

It is key to understand which scripts should be deferred for best performance. Any non-essential script that does not impact the initial rendering of a page should be deferred. Some script types to consider are:

  • Analytics and tracking scripts which are irrelevant to the user’s experience.
  • Third-party integrations such as chat widgets.
  • Scripts that enhance interactivity but aren’t required for the first paint. Such as sliders, menus or forms below the fold.

By deferring these scripts, the browser can focus on rendering the page faster.

Be cautious when deferring scripts in client-side JavaScript applications. If the main content relies on JavaScript to load, deferring the script that initializes the app can delay rendering, making the page feel slower for users. Ensure that scripts responsible for fetching and displaying content are prioritized so the application remains fast and responsive.

LCP initiator scripts

Largest Contentful Paint (LCP) initiator scripts should not be deferred as this could postpone the LCP element rendering. In the example below, we can see a LCP request chain containing two scripts.

Instead of deferring scripts, you could consider using async for non-blocking execution. Add fetchpriority="high" to the script if you know that it's required to display the LCP element.

Preloading the LCP image also helps improve page load time if JavaScript is required to create the actual page element responsible for the Largest Contentful Paint.

LCP initiator request chain

Can you defer inline scripts?

The defer attribute doesn't work with inline scripts, only external scripts. When the browser encounters a script tag within the HTML document, the script executes immediately upon discovery. Applying the defer attribute to an inline script has no effect, since the browser executes the script during the parsing process.

Deferring execution with DOMContentLoaded

Although defer doesn’t work for inline scripts, you can achieve the same effect by wrapping your code inside an event listener for the DOMContentLoaded event. Here’s how:

<script>
document.addEventListener("DOMContentLoaded", () => {
// Inline script
});
</script>

This approach ensures that your script runs only after the HTML document has been completely parsed, similar to how external scripts are deferred with the defer attribute.

How does defer fit into the page load process?

The browser downloads external scripts with the defer attribute in parallel with parsing the HTML document. Execution then occurs once the document has been fully parsed.

This process differs from scripts without the defer attribute that execute immediately when discovered. However, deferred scripts still maintain their order of execution. Meaning if multiple deferred scripts are included, they will execute in the order they appear in the HTML document.

Let’s take a look at some examples using defer and event listeners for scripts in different scenarios:

Example 1: execution order without defer

On this page, we have three external scripts that execute in order. We have also added console logs for both the DOMContentLoaded event and Load event.

Viewing the console, we can see the scripts execute in the expected order.

Page load process for no deferred scripts

Example 2: script 1 deferred

When deferring the first script, the script executes after script 2 and script 3.

The DOMContentLoaded event fires after the deferred script has run.

Page load process for a deferred script

Example 3: adding an inline script

In this example, we have added an inline script between script 1 and script 2 creating a h2 element. Script 1 still has the defer attribute from the previous example. The inline script executes first, followed by script 2, script 3 and then script 1.

Page load process with an inline script

Example 4: running code in the DCL event handler

In this final example, we have added the DOMContentLoaded event listener event to delay the execution of the inline script. We can see that script 1 executes after script 2 and script 3 as expected. The inline script becomes the final script to execute, after the DOMContentLoaded event.

Page load process with an inline script wrapped with the DOMContentLoaded event listener

What if inline scripts depend on a deferred script?

When inline scripts depend on deferred scripts, such as jQuery or other libraries, there may be issues with the page load. This is because the inline script may execute before the deferred script has fully loaded.

If the inline script tries to access global variables or functions that have not yet been defined. The inline script will fail.

Example: Uncaught ReferenceError: $ is not defined

On this page we can see an example where an inline script depends on a deferred script. The following error appears in the console: Uncaught ReferenceError: $ is not defined

Console error

If we inspect the HTML document we can see the inline script that depends on the deferred script.

This script selects .vimeo-video elements using jQuery ($), initializes a Vimeo player, and hides the .vimeo-preloader when a video starts playing. As the jQuery script is deferred. The inline script runs before jQuery loads, causing the "$ is not defined" error.

<script>
$(".vimeo-video").each(function () {
let iframeVideo = $(this);
let iframePlayer = new Vimeo.Player(iframeVideo[0]);
iframePlater.on("play", function () {
setTimeout(function () {
$(iframeVideo).siblings(".vimeo-preloader").css("display", "none");
}, 500);
});
});
</script>

Wrapping the inline script with the DOMContentLoaded event listener will resolve the problem, as this ensures that the inline script only runs after the entire HTML document is parsed, and the deferred script has loaded.

<script>
document.addEventListener("DOMContentLoaded", function () {
$(".vimeo-video").each(function () {
let iframeVideo = $(this);
let iframePlayer = new Vimeo.Player(iframeVideo[0]);
iframePlater.on("play", function () {
setTimeout(function () {
$(iframeVideo).siblings(".vimeo-preloader").css("display", "none");
}, 500);
});
});
});
</script>

Deferring scripts and Interaction to Next Paint

By deferring JavaScript execution until after the page has rendered, users can see and interact with the content sooner. If a user attempts to interact with the page while deferred scripts are still executing, the interaction may coincide with ongoing JavaScript processing. This can lead to input delay and reduced responsiveness, which may affect Interaction to Next Paint (INP).

Defer JavaScript for better Core Web Vitals

Deferring JavaScript is a simple way to improve Core Web Vitals. By reducing render-blocking and speeding up page load times, you can improve LCP scores. When the browser prioritizes important content first, users can interact with the page sooner.

Illustration of website monitoringIllustration of website monitoring

Monitor Page Speed & Core Web Vitals

DebugBear monitoring includes:

  • In-depth Page Speed Reports
  • Automated Recommendations
  • Real User Analytics Data