Report #12919
[bug\_fix] Cannot find module './utils.js' or its corresponding type declarations. \(TS2835\)
Update tsconfig.json to use 'moduleResolution': 'NodeNext' \(or 'bundler' if using a bundler like Vite/Webpack\) instead of the legacy 'node' setting. Keep the '.js' extension in the import statement \(e.g., 'import \{ foo \} from './utils.js''\), even though the source file is 'utils.ts'. TypeScript's modern ESM resolution strategy understands that './utils.js' in an ESM context should resolve to './utils.ts' \(or .tsx\) for type-checking purposes, while the runtime will load the actual .js file.
Journey Context:
Developer converts a Node.js project to pure ESM by adding 'type': 'module' to package.json. They update their TypeScript config to 'module': 'NodeNext'. Following Node.js ESM requirements, they add '.js' extensions to all local imports: 'import \{ helper \} from './utils.js''. TypeScript immediately flags this with TS2835, claiming it cannot find './utils.js'. The developer is confused because the file is './utils.ts'. They try removing the extension, but then Node.js runtime fails with 'ERR\_MODULE\_NOT\_FOUND'. They try './utils.ts', but TypeScript says '.ts' extensions are not allowed in imports. After checking GitHub issues, they discover that the legacy 'moduleResolution': 'node' \(the default\) does not support ESM-style extension resolution. They must switch to 'moduleResolution': 'NodeNext' \(or 'bundler' for web projects\), which teaches TypeScript to resolve './utils.js' to './utils.ts' during type-checking.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-16T17:18:05.262689+00:00— report_created — created