How type=“module” Applies Defer in JavaScript

When you include a script using the type="module" attribute, modern browsers automatically treat that script as deferred by default. This article explains how the HTML specification implements this behavior, why JavaScript modules require non-blocking loading, and how module execution integrates with the Document Object Model (DOM) parsing cycle.

The Default Behavior of Standard vs. Module Scripts

By default, standard JavaScript scripts (<script src="...">) are parser-blocking. When the HTML parser encounters a classic script tag, it pauses parsing the HTML document, downloads the script file, executes it immediately, and only then resumes building the DOM.

Adding type="module" to a <script> tag fundamentally alters this loading lifecycle. The browser automatically applies the defer semantic without requiring the explicit defer attribute. The browser initiates the download of the module file in parallel with HTML parsing and waits to execute the code until the entire HTML document has been fully parsed.

Why Modules Are Deferred by Design

JavaScript modules rely on the ECMAScript module (ESM) system, which allows files to import dependencies using import statements.

  1. Dependency Resolution: A single module file often imports other modules, which may in turn import further dependencies. The browser must fetch, parse, and instantiate this entire dependency graph before any code can run.
  2. Performance Protection: If module scripts blocked the HTML parser synchronously while resolving their entire dependency tree over the network, page rendering performance would degrade significantly.
  3. DOM Availability: Modules frequently interact with the fully constructed DOM. Deferring execution guarantees that elements are available in the document without requiring wrappers like DOMContentLoaded event listeners.

Execution Order and the DOM Lifecycle

Module scripts behave identically to classic scripts that have the defer attribute enabled:

Overriding Defer with the Async Attribute

While deferred execution is the default, it is possible to override this behavior by adding the async attribute to a module script:

<script type="module" src="analytics.js" async></script>

When async is added, the module and its dependency tree still download in the background without blocking the parser. However, the script will execute immediately once all required files in the dependency graph are fetched, regardless of whether the HTML parser has finished or what order the script appears in the source code.