Node resolution works great until it doesn't. Then you're fucked.
What Actually Happens When Your Imports Break
Here's the thing nobody tells you: TypeScript will happily compile your code and tell you everything is fine, while Node.js is sitting there going "what the hell is @components/Button
?" The TypeScript module resolution system operates differently from Node.js runtime module resolution.
The error you'll see looks like this:
Error [ERR_MODULE_NOT_FOUND]: Cannot resolve module '@components/Button'
But the file exists. It's right fucking there. You can see it. VS Code autocompletes it and IntelliSense works perfectly. TypeScript compiles it without errors. But Node.js can't find it because TypeScript doesn't transform your path mappings. This leads to VS Code import issues that drive developers crazy.
The Three Ways Module Resolution Breaks
1. The "Works in Dev, Dies in Prod" Special
Your Vite dev server is happy as a clam with your @components
imports. Everything works perfectly. You push to production and suddenly nothing can find anything. This happens because Vite uses more permissive resolution than webpack or whatever your production bundler is using. Each bundler implements different resolution strategies.
I've seen this destroy entire deployments. One team spent 8 hours debugging why their Docker build was failing when it worked fine locally.
2. The "TypeScript Says Yes, Node Says No" Problem
Even worse than dev/prod discrepancies is when TypeScript and Node.js fundamentally disagree about module resolution.
You configure path mappings in tsconfig.json
:
{
"paths": {
"@components/*": ["src/components/*"]
}
}
TypeScript: "Looks good!"
VS Code: "Here's your autocomplete!"
Node.js: "What the fuck is @components?"
This is by design. The TypeScript team decided they don't want to transform imports because it would make the compiled JavaScript "messy." Meanwhile, you're debugging at 3am wondering why your production server can't find modules. The massive GitHub issue about path mapping transforms has thousands of frustrated developer comments, but Microsoft refuses to budge.
3. The ESM Extension Nightmare
But wait, there's more! If you thought path mappings were confusing, try wrapping your head around the ES module extension requirements.
If you're using ES modules, Node.js requires you to import TypeScript files with .js
extensions:
import { helper } from './utils.js'; // Not .ts, not no extension, .js
This breaks everyone's brain the first time they see it. You're importing a .js
file that doesn't exist to reference a .ts
file that does exist. The Node.js ESM spec requires explicit extensions for ES modules, and TypeScript follows that even though it's confusing as hell.
Version-Specific Gotchas That Will Ruin Your Day
Node.js 18.2.0 breaks path resolution for some edge cases with symlinked packages. If you're using a monorepo with Yarn workspaces, you might see intermittent failures that disappear when you upgrade to 18.3.0.
TypeScript 5.0 changed bundler resolution and broke some webpack configurations that worked perfectly in 4.9. The fix is usually updating to `moduleResolution: "bundler"` but good luck finding that in the migration notes. This affects tsconfig-paths-webpack-plugin configurations.
Windows PATH limit will fuck you if you have deep node_modules nesting. You'll get cryptic resolution errors that make no sense until you realize Windows has a 260 character path limit and your module path is 280 characters long.
Production Failure Stories You Need to Hear
Story 1: The Docker Gotcha
Team had working TypeScript with path mappings. Local development: perfect. Docker build: fails immediately. Turns out their Dockerfile was using `FROM node:16` but their local machine had Node 18. Different module resolution behavior between versions.
Story 2: The Symlink Disaster
Monorepo setup with Lerna. Everything worked until they deployed to a production server that didn't preserve symlinks during the Docker build. All cross-package imports broke because Node couldn't follow the symlinks to resolve modules.
Story 3: The Case Sensitivity Bomb
Developer on macOS created imports with mixed case. Worked fine locally. Deployed to Linux server where filesystems are case-sensitive. Half the imports broke with "module not found" errors because Components
and components
are different directories on Linux.
Why This Drives Developers Nuts
The disconnect between what TypeScript tells you and what actually runs is maddening. You'll spend hours debugging something that should be a solved problem. The tooling lies to you - VS Code shows green checkmarks, TypeScript compilation succeeds, and then Node.js gives you the finger.
I've seen senior engineers with 10+ years experience spend entire days on module resolution issues. It's not because they're bad at their jobs. It's because the tooling ecosystem has competing opinions about how imports should work, and you get to figure out the inconsistencies. The TypeScript Discord server and TypeScript subreddit are full of these frustration posts daily.