First we have to understand these questions:
- When is HTML rendering blocked?
- When is JavaScript loading and executing?
- How can we affect the behavior of loading and executing JS files?
When is HTML rendering blocked?
On initial page load, whenever the browser is instructed to load a referenced JS file it stops further processing of HTML until the JS file is loaded and executed. The same goes with CSS files.
<script src="file.js" />
Blocking HTML rendering is bad for two reasons: First, it takes longer until the browser finishes rendering and displaying the content to the user. And second, you’ll get a problem if your JS code tries to access HTML elements that have not already been parsed.
Async JS loading
That’s why you should load files at the bottom of your <body>
. But there is more you can do. The async
property allows you to load a JS file asynchronously, meaning that HTML continues to render while the JS file is loading, but HTML parsing still stops for JS execution.
<script src="file.js" async />
Using async
has a catch: You have no control over when your JS is going to be executed which is a problem if your JS code depends on having other JS files loaded first.
Deferred JS loading
Loading JS files using defer
loads JS code in parallel to the HTML parsing (just like async
), but unlike async
it also defers execution until HTML parsing is done:
<script src="file.js" defer />
That means the order of specifying deferred JS files in your HTML matters.
Loading JS asynchronously and/or deferring its execution becomes far more important when using HTTP/2, because of how HTTP/2 loads files in parallel.
To summarize it: Use async
for any JavaScript unless you want total control over when it executes, in which case you use defer
.