async and defer
Loading scripts without blocking page rendering.
Also known as: async defer, script async, script defer, defer vs async
By default, a <script> tag blocks the browser: it stops parsing the HTML, downloads the script, runs it, and only then continues. Slow scripts delay the page. The async and defer attributes avoid that.
<script src="app.js"></script> <!-- blocks parsing while it downloads and runs -->
<script src="app.js" defer></script> <!-- downloads in parallel, runs after parsing, in order -->
<script src="stats.js" async></script> <!-- downloads in parallel, runs as soon as ready, any order -->
<script type="module" src="app.js"></script><!-- modules are deferred by default -->
| Download | Runs | Order kept | Waits for DOM | |
|---|---|---|---|---|
| (none) | blocks the parser | immediately | yes | no |
defer | in parallel | after the HTML is parsed | yes | yes |
async | in parallel | as soon as downloaded | no | no |
Which to use
deferfor your own app scripts. They can safely use the DOM, and run in the right order. This is the usual default.asyncfor independent scripts that don’t rely on others or the DOM, such as analytics.- Neither, only for the rare script that must run immediately and block (some critical inline code).
Gotchas
asyncorder is unpredictable. If script B needs script A, don’t make both async.- Inline scripts can’t use
deferorasync. Only external ones withsrc. - Putting scripts at the end of
<body>was the old workaround.deferin the head is better, because downloading starts earlier. DOMContentLoadedfires after deferred scripts run. Accessing an element too early givesnull.- Third-party scripts can still slow things (and run on the main thread), so audit them (render-blocking resources, Core Web Vitals).
See the critical rendering path for why blocking matters.