Join our Newsletter — 33% off our NHI Course

What do teams get wrong about using __dirname in ES module code?

The most common mistake is assuming CommonJS globals still exist in ESM, then patching around the resulting runtime error with brittle boilerplate. Another error is treating import.meta.url as a path string instead of a URL reference. Teams should also remember that import.meta.dirname is only available for file-based modules, not in browser contexts.

Why __dirname Breaks in ES Modules

ES module code does not expose CommonJS globals such as __dirname and __filename, so the first mistake teams make is assuming old runtime conventions still apply. In ESM, the module location is represented through import.meta.url, which is a URL reference, not a filesystem path. That distinction matters because path logic, URL logic, and runtime portability are not interchangeable.

The practical issue is not just syntax. Teams often copy CommonJS-era snippets into ESM and then wrap them in conversion boilerplate that works only on one runtime, one bundler, or one deployment target. That creates fragile code paths, especially when modules are expected to run under Node.js, bundlers, test runners, and file-based tooling with slightly different assumptions about location and resolution.

What Teams Usually Misunderstand About Module Location

The clean way to think about ESM is that the module knows its own location as a URL, and any path must be derived deliberately. Treating import.meta.url as if it were already a path is a category error, and it leads to bugs that are easy to miss until code is moved, packaged, or executed in a different environment.

Another common misunderstanding is assuming that a convenience helper is universally available everywhere the module runs. The newer import.meta.dirname style of access is useful in file-based runtimes, but browser modules do not have a filesystem directory in the first place. That means code that appears tidy in Node.js can still be non-portable if the project also targets browsers, edge runtimes, or shared isomorphic code.

  • Use module location as an input to path derivation, not as a path value itself.
  • Keep file-system assumptions out of code that may be bundled for the browser.
  • Prefer explicit conversion when a URL must become a path, rather than relying on implicit coercion.

Standards & Framework Alignment

This section maps relevant standards and security frameworks to the operational risks and controls described in this guidance.

NIST CSF 2.0 and CIS Controls v8 set the governance and control requirements practitioners need to meet.

Framework Control / Reference Relevance
NIST CSF 2.0 PR.AC-1 — Identity Management, Authentication, and Access Control ESM module location handling affects runtime access and trust boundaries.
PR.DS-5 — Data is Protected Misusing module metadata can expose path-sensitive code and degrade portability safeguards.
GV.RM-1 — Risk Management Strategy ESM/CommonJS mismatches create operational risk that should be handled as a portability control issue.
Recommendation — Separate runtime-specific module logic from portable code paths and enforce least-privilege access to file-only helpers. Protect path-sensitive code by converting URLs deliberately and avoiding implicit path handling. Treat module-system assumptions as a defined engineering risk and standardise ESM compatibility rules.
CIS Controls v8 16.4 — Secure Development Environment Avoid brittle compatibility code by validating module behavior across target runtimes.
2.1 — Establish and Maintain a Software Inventory Portable module code should be inventoried by runtime and deployment target before using filesystem-dependent APIs.
Recommendation — Test ESM path logic in every supported runtime and remove environment-specific assumptions from shared code. Track which modules are Node-only, browser-safe, or shared so filesystem helpers are used only where valid.

Practitioner Guidance

What to verify: Check whether the code truly needs a filesystem path, or whether a URL reference is sufficient. If the module is consumed in more than one runtime, validate the location logic in each target rather than trusting a Node-only test run.

Common mistake: The brittle pattern is to emulate __dirname everywhere, then spread that workaround across shared utilities. A better decision is to isolate path-only behavior to server-side code and keep ESM modules that must remain portable free of filesystem-specific assumptions.

What good looks like: The module uses a clear boundary, with URL-based module metadata on the ESM side and deliberate path conversion only where a filesystem API actually requires it. That keeps the code understandable, testable, and less likely to break when moved between environments.

Practitioner takeaway: The key shift is conceptual, not cosmetic: ESM gives you module location as metadata, while filesystem paths are a separate concern that should be derived only when the runtime and deployment model genuinely support it.