HTML Guides for module
Learn how to identify and fix common HTML validation errors flagged by the W3C Validator — so your pages are standards-compliant and render correctly across every browser. Also check our Accessibility Guides.
The HTML living standard defines that scripts with type="module" are always fetched in parallel and evaluated after the document has been parsed, which is the same behavior that the defer attribute provides for classic scripts. Because this deferred execution is an inherent characteristic of module scripts, the spec explicitly forbids combining the two. Including both doesn't change how the browser handles the script, but it signals a misunderstanding of how modules work and produces invalid HTML.
This validation error commonly arises when developers migrate classic scripts to ES modules. A classic script like <script defer src="app.js"></script> relies on the defer attribute to avoid blocking the parser. When converting to a module by adding type="module", it's natural to leave defer in place — but it's no longer needed or allowed.
It's worth noting that the async attribute is valid on module scripts and does change their behavior. While defer is redundant because modules are already deferred, async overrides that default and causes the module to execute as soon as it and its dependencies have finished loading, rather than waiting for HTML parsing to complete.
How to Fix
Remove the defer attribute from any <script> element that has type="module". No other changes are needed — the loading and execution behavior will remain identical.
If you intentionally want the script to run as soon as possible (before parsing completes), use async instead of defer. But if you want the standard deferred behavior, simply omit both attributes and let the module default take effect.
Examples
❌ Incorrect: defer combined with type="module"
<scripttype="module"defersrc="app.js"></script>
The defer attribute is redundant here and causes a validation error.
✅ Correct: module script without defer
<scripttype="module"src="app.js"></script>
Module scripts are deferred automatically, so this behaves exactly the same as the incorrect example above but is valid HTML.
✅ Correct: using async with a module (when needed)
<scripttype="module"asyncsrc="analytics.js"></script>
Unlike defer, the async attribute is permitted on module scripts. It causes the module to execute as soon as it's ready, without waiting for HTML parsing to finish.
✅ Correct: classic script with defer
<scriptdefersrc="app.js"></script>
For classic (non-module) scripts, the defer attribute is valid and necessary if you want deferred execution.
The defer attribute has no effect on <script> elements that have type="module" because module scripts are already deferred by default.
When a browser encounters <script type="module">, it automatically fetches the script in parallel with HTML parsing and executes it after the document has been parsed. This is the same behavior that defer provides for classic scripts. Adding defer to a module script is redundant, and the W3C validator flags it as invalid markup.
Classic scripts (<script src="...">) block HTML parsing by default. The defer attribute changes that behavior so the script loads in parallel and runs after parsing completes. Module scripts adopted this deferred behavior as their default, so the attribute is unnecessary.
The async attribute, on the other hand, does have an effect on module scripts. It causes the module to execute as soon as it finishes loading, rather than waiting for parsing to complete.
Examples
Invalid: defer on a module script
<scripttype="module"src="app.js"defer></script>
Valid: module script without defer
<scripttype="module"src="app.js"></script>
Remove the defer attribute. The script will behave exactly the same way, since module scripts are deferred by default.
Validate at scale.
Ship accessible websites, faster.
Automated HTML & accessibility validation for large sites. Check thousands of pages against WCAG guidelines and W3C standards in minutes, not days.
Pro Trial
Full Pro access. Cancel anytime.
Start Pro Trial →Join teams across 40+ countries