Stop Copy-Pasting That __dirname Boilerplate
7 mins read

Stop Copy-Pasting That __dirname Boilerplate

I deleted a code snippet from my personal library yesterday. You know the one. That three-line monstrosity we’ve all been pasting into the top of our Node.js files since we started migrating to ES Modules a few years back. It was a rite of passage. You switch a file to .mjs or add "type": "module" to your package.json, you feel modern, you feel clean, and then—*bam*. ReferenceError: __dirname is not defined So you sigh, you Google it (even though you’ve Googled it fifty times), and you grab the boilerplate:
import { fileURLToPath } from 'url';
import { dirname } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
I’m done with it. It’s 2026. We don’t need to live like this anymore. The thing that bugs me isn’t that __dirname disappeared. It had to go. CommonJS and ES Modules handle paths differently at a fundamental level—CommonJS injects variables like __dirname into the wrapper function of every module, while ESM is statically analyzed and doesn’t wrap modules the same way. The variable simply doesn’t exist in the scope. It wasn’t an oversight; it was a design choice to keep the module system platform-agnostic (think browsers, Deno, etc.). But explaining that to a junior dev who just wants to read a JSON file relative to the current script is exhausting. They don’t care about the spec. They care that their code broke. Here is the reality of the situation right now, and why I’m finally cleaning up my snippets folder.

The Native Fix We Ignored

Node.js logo - Green Grass, Nodejs, JavaScript, React, Mean, AngularJS, Logo ...
Node.js logo – Green Grass, Nodejs, JavaScript, React, Mean, AngularJS, Logo …
If you are running anything resembling a modern Node version—and since we are in 2026, you should be on Node 22 (LTS) or at least Node 20—you have native support for this. We stopped needing the boilerplate a while ago, but old habits die hard. Node 20.11.0 and 21.2.0 quietly dropped the solution we actually wanted. It’s built right into import.meta.
// No imports needed. No 'url' or 'path' modules.
console.log(import.meta.dirname);
console.log(import.meta.filename);
That’s it. I caught myself reviewing a PR last week where a colleague had imported path and url just to reconstruct a path variable that already existed. I almost let it slide because, hey, it works. But it’s clutter. It’s noise. It’s five lines of code to do what zero lines of code should do. If you are maintaining a legacy project stuck on Node 18 (my condolences), you are stuck with the boilerplate. But for greenfield projects? Or anything you’ve updated in the last two years? Stop it.

When You Actually Need the “Old” Way

Okay, I lied. I didn’t delete the snippet entirely. I just moved it to a folder named “Legacy.” There are edge cases where import.meta.dirname behaves slightly differently than you might expect, or where you’re working in an environment that strictly adheres to browser-compatible standards. Browsers, obviously, have no concept of a “dirname” in the filesystem sense. If you are writing isomorphic code—stuff meant to run in Node, Deno, *and* Chrome—relying on import.meta.dirname might bite you because it’s a Node-specific extension. In those specific, messy scenarios, sticking to standard URL manipulation is safer because import.meta.url is part of the ECMAScript standard.
// The "Standard" way that works across more runtimes
const fileUrl = new URL('./config.json', import.meta.url);
// usage: await readFile(fileUrl);
Notice I didn’t convert it to a string path there. Most Node fs APIs accept URL objects now. You don’t even need __dirname half the time. You just think you do because you’re used to string concatenation. I ran into this recently while refactoring a CLI tool. I was manually building paths with path.join(import.meta.dirname, 'templates', 'init.js'). It felt clunky. Switching to new URL() felt cleaner, albeit a bit weird if you’re used to seeing file paths as strings.

Why This Still Trips People Up

Node.js logo - AWS Node JS MongoDB Deployment: 2 Easy Methods | Hevo
Node.js logo – AWS Node JS MongoDB Deployment: 2 Easy Methods | Hevo
The confusion stems from muscle memory. We spent a decade typing require and module.exports. Those patterns are burned into our retinas. When we see import, our brains expect a 1:1 mapping of features. We expect __dirname to be there because it feels like a global variable, even though it never was (it was a local argument). When that ReferenceError hits, it feels like a regression. “Why did they break this?” is the common refrain in Slack channels. They didn’t break it. They fixed the module system. CommonJS was a synchronous, disk-bound system that held Node back from aligning with the web. ESM is the standard. It’s asynchronous-friendly, static, and analyzable. The loss of magic variables like __dirname was the price of admission. But honestly? It was a steep price for developer experience in the early days. I remember trying to use ESM in Node 12 or 14. It was a nightmare of experimental flags and confusing errors. We are lucky to be past that.

A Note on JSON Imports

While we’re talking about things we used __dirname for—90% of the time, it was to load a package.json or a config file. “I just need to read the version number,” you say. So you do: const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf-8')) Gross. Import attributes are stable now. Just do this:
import pkg from './package.json' with { type: 'json' };

console.log(pkg.version);
No fs. No path. No __dirname. I tried this on a project last Tuesday and it felt like cheating. I kept waiting for the linter to yell at me or the runtime to throw a syntax error. It didn’t. It just worked. (Note: syntax might vary slightly depending on your exact TS version or bundler, assert vs with, but the platform is settling on with).

The Bottom Line

computer programming code - Computer programming | AnyQuestions
computer programming code – Computer programming | AnyQuestions
If you’re still pasting that fileURLToPath snippet into every new file you create, check your Node version. If you’re on Node 20.11+, use import.meta.dirname. If you’re loading JSON, use import attributes. If you’re dealing with cross-platform URL logic, use new URL(). Let the old boilerplate die. It served us well during the transition years, but we aren’t transitioning anymore. We’re here.

Leave a Reply

Your email address will not be published. Required fields are marked *