How to Fix TypeScript Module Resolution Errors
module-resolutiontypescript-errorsesmimportstsconfig

How to Fix TypeScript Module Resolution Errors

TTypeScript Page Editorial
2026-06-11
10 min read

A practical troubleshooting guide to fixing TypeScript module resolution, import, alias, and ESM errors without guesswork.

TypeScript module resolution errors are rarely caused by one thing alone. They usually appear when your compiler settings, runtime, package format, editor cache, and import paths stop agreeing with each other. This guide gives you a practical way to diagnose those failures, fix the most common import errors, and keep your setup current as ESM defaults, package exports, bundlers, and framework conventions continue to change.

Overview

If you are seeing errors like Cannot find module, Cannot find name 'require', missing type declarations, broken path aliases, or confusing ESM import failures, the first useful step is to stop treating them as random. Module resolution in TypeScript follows a system. Once you know which layer is failing, most fixes become straightforward.

In practice, there are five layers worth checking:

  1. Your import statement: is the path correct, does the file exist, and does the specifier match the package's public entry points?
  2. Your tsconfig.json: especially module, moduleResolution, baseUrl, paths, types, and include.
  3. Your runtime or bundler: Node, Vite, Next.js, Jest, ts-node, Bun, and other tools may resolve modules differently.
  4. The package you are importing: modern packages often use exports, dual ESM/CJS builds, or separate type entry points.
  5. Your editor and tooling cache: sometimes the code is fixed but your language server still shows stale errors.

A common mistake is fixing the TypeScript compiler while leaving the runtime broken, or fixing the runtime while TypeScript still cannot find types. You want both sides to agree.

As a rule of thumb:

  • Use relative imports to verify the file graph first.
  • Use path aliases only after basic resolution works.
  • Match TypeScript module settings to your real runtime, not to a copied snippet.
  • Prefer current package entry points over deep imports into internal files.

If you need a broader baseline for compiler settings, see tsconfig.json Best Settings by Project Type. If your issue is specifically alias-related, the companion guide TypeScript Path Aliases Guide for Vite, Next.js, Node, Jest, and ts-node goes deeper.

Maintenance cycle

The best way to keep module resolution problems manageable is to treat them as a setup area that deserves regular review, not a one-time configuration task. A working config can age badly when you upgrade Node, TypeScript, a framework, or a package manager.

A simple maintenance cycle looks like this:

1. Review resolution settings during dependency upgrades

Each time you make a meaningful upgrade to TypeScript, Node, Vite, Next.js, Jest, ts-node, or your package manager, re-check the following:

  • compilerOptions.module
  • compilerOptions.moduleResolution
  • compilerOptions.target
  • compilerOptions.baseUrl and paths
  • whether your runtime expects ESM or CommonJS
  • whether any package now exposes only public exports

Many import errors appear after an upgrade not because the code changed, but because the resolver became stricter or started honoring a package boundary that older tooling ignored.

2. Keep one canonical module strategy per project

Projects become fragile when they mix incompatible assumptions. For example, a codebase may use CommonJS-style habits, ESM package settings, path aliases understood only by the bundler, and test tooling configured differently from the app.

Pick a clear baseline:

  • Node backend: decide whether the runtime is fundamentally ESM or CommonJS and configure TypeScript to match.
  • React or frontend app: let the framework or bundler lead, then make TypeScript align with it.
  • Monorepo: standardize import boundaries and shared tsconfig presets so packages do not drift apart.

For monorepo-specific concerns, TypeScript Monorepo Setup Guide: pnpm, Project References, and Shared Types is a useful companion.

3. Re-run a small resolution checklist before debugging deeply

Before changing five config files, check these basics:

  • Did you install the package in the current workspace?
  • Does the import path match letter case exactly?
  • Is the file included by tsconfig?
  • Did you restart the TypeScript server or dev process?
  • Are you importing a package subpath that is not exported?
  • Is your alias configured in both TypeScript and the runtime or test tool?

This catches a surprising number of errors early.

4. Schedule a periodic config audit

For active teams, a quarterly audit is often enough. Review:

  • deprecated config patterns copied from old starter repos
  • aliases that no longer match folder structure
  • test runners and scripts that use different module assumptions
  • packages that now ship their own types, making old @types packages unnecessary

Think of this as preventative maintenance. Resolution errors are easier to prevent than to untangle under release pressure.

Signals that require updates

You should revisit your module resolution setup when certain recurring symptoms appear. These are less about one error message and more about drift between tools.

Your editor and build disagree

If VS Code shows Cannot find module but the app runs, or the app fails while TypeScript appears happy, your compiler and runtime are resolving imports differently. This usually points to one of three things:

  • path aliases configured only in one place
  • a framework or bundler handling resolution that plain TypeScript does not know about
  • stale editor cache or the wrong workspace TypeScript version

Restarting the TypeScript server can help, but if the mismatch returns, inspect the config rather than trusting the temporary fix.

ESM errors start appearing after an upgrade

If a project worked before and now fails with import-extension complaints, package export issues, or require vs import confusion, an upgrade likely changed expectations around ESM. This is common when:

  • Node version changes
  • TypeScript changes how it models modern Node resolution
  • a dependency moves to ESM-first publishing
  • a framework updates its starter defaults

In these cases, do not patch around every import one by one. Reassess your project's overall module strategy first.

Path aliases work in development but fail in tests or scripts

This is a classic sign that aliases are only understood by one tool. TypeScript may accept the import because of paths, but Jest, ts-node, Node, or another script runner does not automatically inherit that mapping.

The fix is not usually in the import statement itself. The fix is making the rest of your toolchain understand the same alias rules, or deciding that aliases are not worth the extra configuration for that environment.

Deep imports into packages begin to fail

If code imports from internal package paths such as some-lib/dist/utils or some-lib/lib/internal, it may break once the package adopts stricter exports. Even when these deep imports compile, they are brittle. Prefer the package's documented entry points.

New team members hit setup issues immediately

If fresh clones regularly produce module errors, the problem is often structural rather than local. Missing generated files, inconsistent package manager usage, undocumented aliases, or hidden editor assumptions can all be involved. This is a strong signal to simplify and document your setup.

Common issues

This section covers the problems that come up most often when fixing a TypeScript module resolution error, along with practical ways to narrow them down.

1. Cannot find module 'x' or its corresponding type declarations

This message can mean several different things, so separate them:

  • The package is not installed: check the current workspace, especially in a monorepo.
  • The package is installed but types are missing: some packages bundle their own types, others rely on separate declarations, and some have incomplete typing support.
  • You are importing an unsupported subpath: the package may expose only certain entry points.
  • Your resolver mode is wrong: TypeScript may be using a resolution strategy that does not match the package format.

Practical checks:

  1. Confirm the package exists in the relevant package.json.
  2. Try importing from the package root instead of a deep path.
  3. Inspect whether the package publishes types or whether you need compatible declarations.
  4. Review moduleResolution in tsconfig.json.

If your issue is broader than modules and involves missing globals or environment names, How to Fix "Cannot find name" and Other Missing Type Errors in TypeScript may help.

2. Path alias imports compile in one place but fail elsewhere

Aliases such as @/components/Button improve readability, but they are one of the most common causes of TypeScript path resolution confusion.

Remember: paths in TypeScript helps the compiler understand aliases, but it does not automatically make every runtime, test runner, or build tool understand them the same way.

Check all three layers:

  • TypeScript: baseUrl and paths
  • Bundler/runtime: Vite, Webpack, Next.js, Node loaders, ts-node, or another resolver
  • Tests and scripts: Jest, Vitest, CLI scripts, lint tooling, codegen tools

If the same alias is duplicated in multiple places, drift becomes likely. Centralize when possible, and avoid aliases that obscure package boundaries in monorepos.

For deeper examples across environments, see TypeScript Path Aliases Guide for Vite, Next.js, Node, Jest, and ts-node.

3. ESM TypeScript errors in Node projects

Node projects are especially sensitive because both compile-time and runtime rules matter. Problems often include:

  • mixing require and import inconsistently
  • using a module setting that does not reflect the runtime
  • importing files with assumptions about extension handling that no longer hold
  • using tools that emulate Node differently in development and production

A stable fix usually starts by choosing one path:

  • CommonJS-oriented setup for older ecosystems or simpler compatibility
  • ESM-oriented setup for modern Node projects and packages built around import/export

Once chosen, make sure your package settings, tsconfig, scripts, and test environment all align with that decision. Avoid hybrid half-migrations where the source code says ESM but supporting tools still assume CommonJS.

For a practical baseline, Node.js + TypeScript Setup That Still Works in 2026 is a good follow-up.

4. Framework setup hides the real resolution rules

In React, Next.js, and similar environments, the app may compile because the framework resolves imports for you, while separate tools do not. You may not notice the problem until ESLint, Jest, Storybook, or a standalone script complains.

When this happens, ask which tool is the source of truth. If the framework defines the resolution behavior, mirror that in TypeScript and your supporting tools. If not, simplify the setup so the project does not depend on hidden conventions.

If you are still stabilizing a frontend project baseline, see React + TypeScript Setup Guide for Vite, Next.js, and CRA Alternatives.

5. baseUrl and paths are doing too much

These options are useful, but they can become a crutch. Teams sometimes use broad aliases to bypass clear module boundaries, import across layers freely, or hide circular dependencies. The result is not just resolution pain, but architectural confusion.

Good uses of aliases:

  • stable app-level imports from well-defined roots
  • shared package entry points in a monorepo
  • avoiding long chains of ../../.. in large apps

Risky uses:

  • multiple aliases pointing to overlapping directories
  • aliases that differ between app, tests, and scripts
  • imports that bypass package public APIs

If aliases are making debugging harder, reduce them before adding more config.

6. The wrong files are included in the TypeScript program

Sometimes the module exists, but TypeScript still cannot see it because your project boundaries are off. Review:

  • include and exclude
  • whether generated declarations are produced where TypeScript expects them
  • whether a package reference or workspace dependency has actually been built
  • whether you are opening the right folder in the editor

This comes up often in monorepos and in backend projects with separate source and build directories.

7. Editor cache and stale language server state

Not every module error is real. Editors can hold onto outdated project graphs, especially after renames, branch switches, dependency installs, or tsconfig edits.

Before concluding the config is broken:

  1. restart the TypeScript server
  2. restart the dev process
  3. remove build artifacts if they can confuse the project graph
  4. make sure the editor is using the workspace TypeScript version

If the problem disappears only temporarily, you still have a configuration mismatch somewhere. But it is worth ruling out stale state first.

When to revisit

The practical rule is simple: revisit module resolution every time your project changes how it loads code, publishes code, or shares code. That includes tool upgrades, package format changes, new aliases, monorepo restructuring, new test runners, and framework migrations.

Use this action-oriented checklist when the next error appears:

  1. Identify the failing layer. Is the error from TypeScript, Node, the bundler, the test runner, or the editor?
  2. Test a plain relative import. If a relative path works, the issue is likely aliasing or package entry configuration.
  3. Check tsconfig.json against reality. Make sure module and moduleResolution reflect your actual runtime and toolchain.
  4. Inspect package boundaries. Avoid deep imports unless the package explicitly documents them.
  5. Verify aliases everywhere. TypeScript, runtime, and tests must agree.
  6. Clear stale state. Restart the TypeScript server and relevant processes.
  7. Document the fix. If one developer hits the issue, others probably will too.

It is also worth scheduling a recurring review when any of these are true:

  • you upgraded TypeScript or Node recently
  • you adopted ESM or changed package type
  • you added a new bundler, test runner, or script environment
  • you introduced or expanded path aliases
  • you moved to a monorepo or changed package boundaries

Finally, resist the urge to solve every module resolution error with a local patch. The durable fix is usually to make your compiler, runtime, and package structure agree with each other. That takes a little more thought up front, but it pays off in fewer recurring import problems and a project that remains understandable months later.

For related maintenance work, you may also want to review ESLint + TypeScript Flat Config Guide, Interface vs Type in TypeScript: Current Best Practices, and How to Type API Responses in TypeScript Without Lying to the Compiler so your broader TypeScript setup stays consistent as the ecosystem evolves.

Related Topics

#module-resolution#typescript-errors#esm#imports#tsconfig
T

TypeScript Page Editorial

Senior SEO Editor

Senior editor and content strategist. Writing about technology, design, and the future of digital media. Follow along for deep dives into the industry's moving parts.

2026-06-17T08:01:38.118Z