Skip to main content

Improve Page Speed With Async And Defer

· 8 min read

The async and defer attributes exist to help you make your website load faster. This article explains what these attributes do and when you should use them.

What problem do the async and defer keywords address?

By default script tags are render-blocking, which means that the browser won’t display any page content until after the script has run. When the browser’s HTML parser encounters a script it downloads and executes it before continuing to process the rest of the page.

The async and defer attributes tell the browser that these scripts don’t have to run immediately and the browser can process the rest of the page first and then run these scripts later on.

Network request waterfall showing render-blocking and async script tags

What does the async attribute do?

The async attribute ensures that the JavaScript file is loaded asynchronously in the background and does not block rendering. The script is then run as soon as it has been downloaded, with no regard for the order that the scripts were found in the document.

An async-loaded script tag looks like this: <script src="app.js" async>.

What does the defer attribute do?

The defer attribute tells the browser to run the script after the document has been parsed. As with the async attribute this ensures that the page can render in the meantime.

Once the document has been parsed the deferred scripts are run in the same order as they were found in the document. The DOMContentLoaded event indicates that the document has been parsed and all deferred scripts have been run.

An deferred script tag looks like this: <script src="app.js" defer>.

An example of the async and defer attributes in action

Let’s take a closer look at how async and defer work in practice and what it means for page speed.

Rendering behavior without async and defer

We’ll load two JavaScript files and add console logs at the end of the HTML and when the DOMContentLoaded event fires.

<script>
addEventListener(
"DOMContentLoaded",
() => console.log("DOMContentLoaded")
);
</script>
<script src="/script1.js"></script>
Top of page
<br/>
<script src="/script2.js"></script>
Bottom of page
<script>console.log("End of Body")</script>

The request waterfall then shows how the page content appears gradually. At first no content is shown until after the first script has loaded. When that happens the page renders partially and the “top of page” text becomes visible.

Request waterfall showing the impact of blocking scripts on rendering

The “bottom of page” text is placed below the second script, which means it only becomes visible after the second script has been downloaded and run by the browser. I’ve made script2.js larger than script1.js to make it take longer to download and demonstrate this gradual loading behavior.

In the console we can see that everything is executed in order:

Script 1
Script 2
End of Body
DOMContentLoaded

If the two scripts are render-blocking, why do they start downloading at the same time?

This is because browsers use a preload scanner in addition to the primary HTML parser. The preload scanner identifies upcoming resources ahead of time and starts downloading them early.

Without the preload scanner each file would have to finish loading before the next resource is discovered. Instead, browsers start downloading the following resources early, and when the HTML parser discovers them they often have already finished downloading and can be run right away.

Adding async and defer

Now let’s see what happens when we add the defer attribute to the first script and async to the second one.

<script src="/script1.js" defer></script>

<script src="/script2.js" async></script>

The two scripts now no longer block rendering, which means that all page content appears as soon as the document HTMl has been downloaded.

Request waterfall showing the impact of async

This is also reflected in the order of execution as we can see by the console output. We reach the “End of Body” inline script quickly and then the deferred script runs.

End of Body
Script 1
DOMContentLoaded
Script 2

The DOMContentLoaded event indicates that the document has been fully parsed and all deferred scripts have been run. It does not wait for the async script to finish downloading, and script 2 appears last in the console.

Switching async and defer

What if we switch around the async and defer attributes, and also change the order of script 1 and 2?

<script src="/script2.js" defer></script>

<script src="/script1.js" async></script>

This does not change anything visually in our example – neither script is render-blocking. And because of how the async and defer attributes work, script 1 still runs ahead of script 2, even though it’s positioned ahead of script 1 in the code.

End of Body
Script 1
Script 2
DOMContentLoaded

The async script (script1.js) runs as soon as it’s finished downloading. If we wanted to make script 1 and 2 run in order we could use the defer attribute for both.

The DOMContentLoaded event doesn’t fire until after the deferred script (script2.js) has run.

How to check if scripts are using defer and async

Run a speed test on your website to see whether scripts on your site block rendering. Open the Requests tab to see which scripts block rendering and which use the async or defer attribute.

Request waterfall showing a mix of render-blocking and async/defer resources

You can also use Lighthouse-based tools like PageSpeed Insights to check which of your resources are render-blocking. Open the performance Opportunities section and look for the Eliminate render-blocking resources audit.

Screenshot of the Lighthouse render blocking resources audi

JavaScript async vs defer: which one should you use?

Both attributes make sure scripts don’t block rendering and help you optimize the First Contentful Paint metric. In most cases async is a good choice because it still loads and executes the code as soon as possible.

Use defer if you have multiple scripts that depend on each other. defer also means that the code will run slightly later than with async. That can be good if you want to leave the CPU main thread free for more important work. But it can also mean that functionality takes longer to become available to the user. If the user navigates away before the browser has finished parsing the document the script will not run at all, so you should prefer async for analytics scripts.

How are async and defer different from placing scripts at the end of the body?

Placing scripts at the end of the body was common before the browsers supported the HTML async and defer attributes. It ensures that the script doesn’t block the page from rendering.

If your HTML document is large then putting scripts at the end of the body element can mean it takes longer for the browser to discover the resource, as the full HTML resource needs to finish downloading first. In contrast, an async script in the page head would quickly be discovered by the preload scanner.

Browsers also can’t tell that a script at the end of the body element isn’t render-blocking, so they’ll assume it is and assign a high-priority to the request. This may help load your script more quickly, but could also increase bandwidth competition with render-blocking resources like CSS stylesheets. As a result these other resources may download more slowly.

Do the async and defer attributes impact SEO?

Google doesn’t look at the attributes directly, but using them can help you optimize page speed and Core Web Vitals. The Core Web Vitals became a ranking factor in 2021, so making your website load faster not only helps you deliver a better user experience but also helps you rank higher in Google.

You can use Google Search Console to see how well your website scores on the Core Web Vitals metrics.

Core Web Vitals data in Google Search Console

Using async together with preload resource hints

Using the async attribute lowers the priority of the request, but that’s not always desirable if the JavaScript file renders important content on the page. Because of that websites sometimes include both an async script script and a preload resource hint for the same file.

<link rel=”preload” href=”app.js” as=”script”>
<script src=”app.js” async>

The preload tag ensures that the application code is loaded with high priority. However, this can sometimes lower page speed when the app download competes with more render-blocking resources on the page. Run a speed test on your website to see whether important resources download more slowly due to bandwidth competition.

Async behavior when inserting script tags dynamically

Script tags that are created dynamically using document.createElement and inserted using appendChild or a similar function load asynchronously by default.

var script = document.createElement("script");
script.src = "https://code.jquery.com/jquery-3.7.0.js";
console.log(script.async); // outputs "true"

However, using document.write in a blocking script tag will create a chain of render-blocking requests unless the async attribute is added explicitly.

document.write(`<script src="https://code.jquery.com/jquery-3.7.0.js">`)

Network request chain with document.write

Monitor the page speed and Core Web Vitals of your website

Ensuring that requests don’t block rendering and load with the right priority is just one aspect of making sure your website delivers a good experience to users. DebugBear can provide a detailed performance analysis of your website and monitor site speed and Core Web Vitals over time.

Start a free 14-day trial today.

web performance dashboard

Get a monthly email with page speed tips