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.
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.
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.
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.
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.
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.
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">`)
Can async and defer make the Largest Contentful Paint worse?
If your page content relies on JavaScript code to render the main page content then using async
or defer
for those scripts can slow down your website. For example, the code might be used to initiate a slider to to render a web application.
In that case you can:
- Find a way to render the LCP element without the JavaScript code
- Use a
fetchpriority=high
hint on thescript
attribute to ensure the file is still loaded quickly - Just remove
async
ordefer
if the overall impact is negative
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, monitor site speed and Core Web Vitals over time, and track the impact of your performance on real users.