TS2307: Cannot find module after setting up path aliases
Path aliases in tsconfig.json can break module resolution. Here's how to fix it in under 30 seconds, or dig deeper if needed.
You just set up path aliases in tsconfig.json, and now TypeScript screams TS2307: Cannot find module '@utils/foo'. I've been there. This tripped me up for an hour my first time too.
The good news? You're probably missing one small config detail. Let's walk through the fixes from quickest to deepest. Stop when your error's gone.
30-second fix: Check baseUrl and paths order
Open your tsconfig.json. Look for the compilerOptions section. You need baseUrl set before paths. And baseUrl must be a dot-relative path to the root of your source files — usually "./src" or "./app".
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"@utils/*": ["utils/*"],
"@components/*": ["components/*"]
}
}
}
Notice the * in both the alias and the target path. If you wrote "@utils": ["utils/*"] (no star on the left), TypeScript won't map subpaths. Also make sure the target path doesn't start with ./ — just the relative folder name works.
If that's correct and you're still seeing TS2307, move to the 5-minute fix.
5-minute fix: Tell your bundler or runner about the aliases
TypeScript only understands these aliases at compile time. If you're using webpack, Vite, Jest, Nuxt, or any other tool, they need their own alias config. Here's where it usually breaks:
Webpack 5
In your webpack config (usually webpack.config.js), add a resolve.alias block:
const path = require('path');
module.exports = {
resolve: {
alias: {
'@utils': path.resolve(__dirname, 'src/utils/'),
'@components': path.resolve(__dirname, 'src/components/')
}
}
};
Vite
In vite.config.ts, use the resolve.alias option:
import { defineConfig } from 'vite';
import path from 'path';
export default defineConfig({
resolve: {
alias: {
'@utils': path.resolve(__dirname, 'src/utils'),
'@components': path.resolve(__dirname, 'src/components')
}
}
});
Jest (for unit tests)
Jest won't touch your tsconfig aliases. You need moduleNameMapper in your Jest config:
module.exports = {
moduleNameMapper: {
'^@utils/(.*)$': '/src/utils/$1',
'^@components/(.*)$': '/src/components/$1'
}
};
Nuxt 3
Nuxt uses nuxt.config.ts with an alias property. But Nuxt already respects tsconfig paths — if it doesn't, your tsconfig might not be in the root. Double-check that.
If after this you still get TS2307, it's time for the advanced fix.
15+ minute fix: Module resolution mode and environment mismatch
This one bit me on a Node.js project using ES modules. TypeScript's path resolution behaves differently based on module and moduleResolution settings. Here's the ugly truth:
- If
moduleis"commonjs", TypeScript uses"classic"resolution by default — which ignores paths. You must setmoduleResolution: "node". - If
moduleis"esnext"or"es2020", TypeScript uses"node"resolution by default — paths work. - If you're using
"node16"or"nodenext"resolution, paths still work, but yourbaseUrlis ignored in some TypeScript versions (≥4.7). You need to setbaseUrl: "./src"explicitly.
Check your tsconfig.json:
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "node",
"baseUrl": "./src",
"paths": {
"@utils/*": ["utils/*"]
}
}
}
If you changed moduleResolution to "node" and it still fails, try the exact target path with a ./ prefix:
"@utils/*": ["./utils/*"]
Real-world trigger: VSCode not picking up the config
Sometimes VSCode caches the old tsconfig. Close all files, run the TypeScript: Restart TS server command (Ctrl+Shift+P), and reopen your project. If you're using a monorepo, make sure your editor is pointing to the right tsconfig — set typescript.tsconfig in your workspace settings.
Windows path case sensitivity
On Windows, paths are case-insensitive, but TypeScript respects the case you wrote. If your folder is src/Utils but you wrote "@utils/*": ["utils/*"], it fails. Match the actual folder name exactly.
One last sanity check
Verify your import statement matches the alias pattern. For "@utils/*": ["utils/*"], you import:
import { helper } from '@utils/helper'; // correct
import { helper } from '@utils'; // might fail — no * on left
If you want to import from the folder root, add a separate entry:
"@utils": ["utils/index"],
"@utils/*": ["utils/*"]
I know this error is infuriating because it's almost always a tiny config typo. Start with the 30-second fix, then hit the bundler config, then check module resolution. You'll find the culprit.
Was this solution helpful?