Script Loading (defer, async, type=module)
Also Known As
TL;DR
Explanation
By default, a <script src='...'> in <head> pauses HTML parsing until the script downloads and executes — blocking the entire render pipeline. Three attributes change this: async downloads in parallel and executes immediately when ready (order not guaranteed); defer downloads in parallel and executes after HTML is fully parsed, in source order; type='module' is deferred by default, scoped to the module, and supports static import/export. The rule of thumb: defer for app scripts that need the DOM or depend on each other, async for fully independent scripts like analytics. Inline scripts ignore both attributes — they always block. Placing scripts at the end of <body> is the old workaround; defer is the modern equivalent with better performance characteristics.
Common Misconception
Why It Matters
Common Mistakes
- <script> in <head> without defer or async — blocks HTML parsing and delays render.
- Using async on scripts that depend on each other (jQuery then a jQuery plugin) — causes intermittent race condition failures.
- defer or async on inline scripts — both attributes are silently ignored; inline scripts always block.
- Not adding type='module' to ES module scripts — modules are deferred by default and enable import/export and tree-shaking.
Avoid When
- Do not use async on scripts that depend on other async scripts — execution order is not guaranteed.
- Do not add defer or async to inline scripts — they are ignored and the script will still block.
When To Use
- Use defer for any script that needs the DOM or has dependencies on other scripts.
- Use async for analytics, ads, or chat widgets that are fully self-contained.
- Use type='module' for ES module-based projects to get deferred execution and import/export support.
Code Examples
<!-- Blocks render — browser stops parsing HTML until jQuery downloads and runs -->
<head>
<script src="jquery.js"></script>
<script src="app.js"></script>
</head>
<!-- defer: parallel download, executes in order after DOM ready -->
<head>
<script src="jquery.js" defer></script>
<script src="app.js" defer></script>
</head>
<!-- async: only for fully independent scripts with no dependencies -->
<script src="analytics.js" async></script>
<!-- type=module: deferred, scoped, supports import/export -->
<script type="module" src="app.mjs"></script>