--- title: "Script loading — defer, async, module" category: performance status: recommended url: https://webspecification.com/spec/performance/script-loading/ updated: "2026-05-29T09:55:11.000Z" sources: - title: "HTML Living Standard — The script element" url: "https://html.spec.whatwg.org/multipage/scripting.html#the-script-element" publisher: "WHATWG" - title: "MDN — ``` Plus the platform extras: `nomodule` for legacy fallback, `crossorigin` for CORS, `integrity` for [SRI](/spec/security/subresource-integrity/), `fetchpriority` for prioritisation hints, `blocking="render"` for the rare case you genuinely need a blocking script. ## Why it matters A ` ``` `app.js` runs before `widget.js`. Both run before `DOMContentLoaded`. **Use `async` for independent third-party.** Analytics, A/B tooling, chat widgets — anything that doesn't depend on your code or DOM order. Always pair with [Subresource Integrity](/spec/security/subresource-integrity/) and `crossorigin="anonymous"` so a compromised CDN cannot ship modified code to your visitors: ```html ``` Some vendors (rolling-release analytics, A/B platforms) ship a script whose contents change at the vendor's discretion and therefore cannot be pinned with SRI. In that case the integrity guarantee becomes "I trust this vendor with my origin" — make that an explicit risk decision, document it, and consider a [strict CSP](/spec/security/content-security-policy/) with a per-deploy allow-list as compensating control. If two third-party scripts depend on each other, neither can be `async`. Use `defer` on both. **Use `type="module"` for modern code.** It's `defer` by default, supports `import`, and gives you a single canonical place for tree-shaking, code-splitting, and dynamic `import()` for route-level lazy loading. ```html ``` **Preload module dependencies.** Modules fetched via `import` are discovered lazily — preload them so the browser starts fetching during initial parse: ```html ``` See [resource hints](/spec/performance/resource-hints/) for the decision table. **Drop the legacy `nomodule` shim.** Every shipping browser in 2026 supports `type="module"`. The `nomodule` fallback was useful in 2018; it now ships a second bundle for users who don't exist. **Inline tiny critical scripts in `
` if and only if** they must run before paint and are small enough that the cost of inlining is less than the cost of a separate request. Theme colour application, CSP setup, FOUC prevention — yes. A 50 KB framework runtime — no. **Combine with `fetchpriority`** for the rare case you have a script that's deferred but critical: ```html ``` **Set CSP allow-listing and SRI** ([subresource integrity](/spec/security/subresource-integrity/)) on every third-party script. The loading attribute does not change those obligations. ## Common mistakes - `