👀 Live Preview
Only one message appears—module browsers skip nomodule:

The nomodule attribute is a boolean HTML attribute on <script> elements. It marks a script as a legacy fallback: browsers that support ES modules (via type="module") skip nomodule scripts, while older browsers that lack module support run them as classic scripts. The typical pattern pairs one modern module script with one nomodule fallback so each visitor loads exactly one bundle. This is progressive enhancement for the transition from pre-module browsers to ES6 modules.
Present = legacy.
On script tags.
Modern pair.
One script runs.
app.mjs + legacy.
Dynamic scripts.
nomodule AttributeThe primary purpose of nomodule is browser compatibility during the ES module era. You ship modern code with <script type="module"> (import/export syntax, strict mode, deferred by default) and a separate transpiled bundle with nomodule for browsers like Internet Explorer 11 that cannot parse modules. Module-capable browsers ignore the fallback, so users never download and execute both.
The attribute does not block module scripts from loading—it marks which classic script is the fallback. The old reference incorrectly suggested it prevents older browsers from loading modules; in reality, old browsers never understood type="module" anyway and treated those tags as unknown, while nomodule scripts ran normally.
Most 2026 projects target browsers with native module support and rely on bundlers (Vite, webpack, etc.) to transpile. Learn nomodule to read legacy code and understand how module fallbacks worked before universal ES module support.
Pair a module script with a nomodule fallback on separate <script> tags:
<!-- Modern browsers: runs this -->
<script type="module">
import { init } from "./app.mjs";
init();
</script>
<!-- Legacy browsers: runs this instead -->
<script nomodule>
// Transpiled bundle for browsers without module support
initApp();
</script>nomodule alone; no value required.<script> elements.src files.defer or async on nomodule scripts.type="module" and nomodule on the same tag.scriptElement.noModule = true (camelCase).The nomodule attribute is boolean—it has no meaningful string value:
nomodule — Attribute present; script runs only in non-module browsers.nomodule="" — Also valid in HTML5; same as bare nomodule.script.noModule = true sets the attribute; false removes legacy-only behavior.const fallback = document.createElement("script");
fallback.noModule = true;
fallback.src = "legacy-bundle.js";
document.body.appendChild(fallback);
// Detect module support
const supportsModules = "noModule" in HTMLScriptElement.prototype;| Use case | Markup | Notes |
|---|---|---|
| Modern script | <script type="module"> | ES modules; deferred |
| Legacy fallback | <script nomodule> | Skipped by module browsers |
| External bundles | src="app.mjs" + nomodule src="legacy.js" | Production pattern |
| Detect support | "noModule" in HTMLScriptElement.prototype | Feature detect |
| Set via JS | script.noModule = true | DOM property |
| Classic script | Plain <script> (no nomodule) | Runs everywhere |
| Element | Supported? | Notes |
|---|---|---|
<script> | Yes | Only valid element |
<link> | No | Use rel="modulepreload" for modules |
<module> | No | Not an HTML element |
<iframe> | No | Scripts load inside document |
type="module" vs nomodule vs classic <script>| Script type | Module browsers | Legacy browsers |
|---|---|---|
type="module" | Runs (deferred) | Ignored (unknown type) |
nomodule | Skipped | Runs as classic script |
| Classic (no attributes) | Runs | Runs |
For dual-bundle setups, use module + nomodule only. Avoid adding a third classic script that would run in modern browsers alongside the module.
Inline module fallback, dynamic script injection, and external src bundle pattern.
Only one message appears—module browsers skip nomodule:
Modern inline module with legacy fallback:
<script type="module">
document.body.insertAdjacentHTML("beforeend", "<p>Loaded using module script.</p>");
</script>
<script nomodule>
document.body.insertAdjacentHTML("beforeend", "<p>Loaded using nomodule fallback.</p>");
</script>The browser evaluates module support first. If modules are supported, the type="module" script runs and every nomodule script on the page is ignored. Legacy browsers skip unknown module types and execute the nomodule classic script instead.
Inject the correct script after detecting module support:
<script>
if ("noModule" in HTMLScriptElement.prototype) {
const mod = document.createElement("script");
mod.type = "module";
mod.src = "app.mjs";
document.body.appendChild(mod);
} else {
const legacy = document.createElement("script");
legacy.noModule = true;
legacy.src = "app-legacy.js";
document.body.appendChild(legacy);
}
</script>The presence of noModule on HTMLScriptElement.prototype indicates module-aware script handling. The old reference example had incorrect operator precedence in its feature detect—always wrap the in check in parentheses or test the prototype directly as shown here.
Production sites ship separate module and legacy bundles:
<script type="module" src="app.mjs"></script>
<script nomodule defer src="app-legacy.js"></script>Build tools generate two outputs from one source: an ES module bundle and a transpiled IIFE/UMD bundle. HTML selects the correct file per browser without user-agent sniffing in JavaScript.
defer on nomodule fallbacks so legacy scripts do not freeze rendering.Browser sees module and nomodule tags.
Can the browser run type=module?
Module script OR nomodule fallback.
Every browser gets working JS.
The nomodule attribute is supported in Chrome 61+, Firefox 60+, Safari 11+, and Edge 16+. Browsers without module support (e.g. IE11) ignore type="module" and execute nomodule scripts. Module-capable browsers skip nomodule entirely.
nomoduleThe nomodule attribute is supported in Chrome 61+, Firefox 60+, Safari 11+, and Edge 16+. Browsers without module suppor…
Bottom line: nomodule targets pre-module browsers. Modern evergreen browsers always take the type="module" path.
app.mjs and app-legacy.js from one codebase.<script nomodule defer src="…"> avoids blocking HTML parsing.type="module" script may be enough.The nomodule attribute enables a clean dual-script pattern: modern browsers load ES modules while legacy browsers run a classic fallback. It is a boolean attribute on <script> that module-aware browsers ignore entirely.
While most new projects rely on transpiled single bundles, understanding nomodule helps you read older progressive-enhancement code and design compatible script loading strategies.
nomoduleBookmark these before your next nomodule implementation.
Boolean attr.
ScopeNon-module browsers.
BehaviorModern pair.
PatterncamelCase DOM.
DynamicNot both.
Loading<script> only. Combine with type="module" on a separate tag for the modern path.type="module" scripts and ignore every nomodule script on the page.script.noModule = true or setAttribute("nomodule", ""). Detect support with "noModule" in HTMLScriptElement.prototype.nomodule scripts are classic scripts and support defer and async like any non-module script.Practice inline fallbacks, dynamic injection, and external bundle patterns in the Try It editor.
5 people found this page helpful