Skip to content

Fix: ESLint import/no-unresolved Error (Module Exists but ESLint Can't Find It)

FixDevs ·

Quick Answer

How to fix ESLint's import/no-unresolved errors when modules actually resolve correctly — configure eslint-import-resolver-typescript, fix path alias settings, and handle node_modules that ESLint cannot find.

The Error

ESLint reports import errors even though the imports work correctly in the application:

error  Unable to resolve path to module '@/components/Button'  import/no-unresolved
error  Unable to resolve path to module '../utils/helpers'     import/no-unresolved
error  Unable to resolve path to module 'some-package'         import/no-unresolved

Or TypeScript path aliases are not recognized:

error  Unable to resolve path to module '@components/Header'   import/no-unresolved
error  Unable to resolve path to module '~/utils'              import/no-unresolved

Or after adding a new package:

error  Unable to resolve path to module 'date-fns'             import/no-unresolved

The build and runtime work fine — only ESLint reports the error.

Why This Happens

The import/no-unresolved rule comes from eslint-plugin-import. It resolves module paths using its own resolver — separate from TypeScript’s module resolution, webpack’s resolve, or Node.js’s require(). By default it uses Node.js resolution rules and does not understand:

  • TypeScript path aliases (@/, ~/, @components/) defined in tsconfig.json
  • webpack aliases defined in webpack.config.js
  • Vite aliases defined in vite.config.js
  • Packages with only type definitions or non-standard exports fields
  • Monorepo packages linked via workspaces

The fix is to install and configure a resolver that matches how your bundler or TypeScript resolves modules.

Fix 1: Install and Configure eslint-import-resolver-typescript

For TypeScript projects, this is the standard fix — it makes eslint-plugin-import use TypeScript’s module resolution:

npm install --save-dev eslint-import-resolver-typescript

ESLint flat config (eslint.config.js — ESLint 9+):

import importPlugin from 'eslint-plugin-import';
import tsParser from '@typescript-eslint/parser';

export default [
  {
    plugins: { import: importPlugin },
    languageOptions: {
      parser: tsParser,
    },
    settings: {
      'import/resolver': {
        typescript: {
          alwaysTryTypes: true, // Always try @types/* packages
          project: './tsconfig.json',
        },
      },
    },
    rules: {
      'import/no-unresolved': 'error',
    },
  },
];

Legacy .eslintrc.js format:

// .eslintrc.js
module.exports = {
  parser: '@typescript-eslint/parser',
  plugins: ['import'],
  settings: {
    'import/resolver': {
      typescript: {
        alwaysTryTypes: true,
        project: './tsconfig.json',
      },
    },
  },
  rules: {
    'import/no-unresolved': 'error',
  },
};

For monorepos with multiple tsconfig files:

settings: {
  'import/resolver': {
    typescript: {
      alwaysTryTypes: true,
      project: [
        './tsconfig.json',
        './packages/*/tsconfig.json',
        './apps/*/tsconfig.json',
      ],
    },
  },
},

Fix 2: Configure Path Aliases Correctly in tsconfig.json

The resolver reads path aliases from tsconfig.json. Ensure they are defined there — not just in Vite or webpack config:

// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@components/*": ["src/components/*"],
      "@utils/*": ["src/utils/*"],
      "@hooks/*": ["src/hooks/*"],
      "~/*": ["src/*"]
    }
  }
}

After updating tsconfig.json, restart your ESLint server (in VS Code: Ctrl+Shift+P → “ESLint: Restart ESLint Server”).

Verify the alias resolves correctly:

# Use eslint with --debug to see resolver output
npx eslint --debug src/App.tsx 2>&1 | grep -i "resolver\|alias\|resolve"

Fix 3: Use eslint-import-resolver-alias for Non-TypeScript Projects

For JavaScript projects using webpack or Vite with path aliases:

npm install --save-dev eslint-import-resolver-alias
// .eslintrc.js
module.exports = {
  settings: {
    'import/resolver': {
      alias: {
        map: [
          ['@', './src'],
          ['@components', './src/components'],
          ['@utils', './src/utils'],
        ],
        extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
      },
    },
  },
};

Or use eslint-import-resolver-vite for Vite projects:

npm install --save-dev vite-plugin-eslint eslint-import-resolver-vite
// .eslintrc.js
module.exports = {
  settings: {
    'import/resolver': {
      vite: {
        configPath: './vite.config.ts',
      },
    },
  },
};

Fix 4: Fix node_modules Resolution Failures

If ESLint cannot resolve a package you have installed:

Check the package actually exists:

ls node_modules/date-fns
ls node_modules/@types/date-fns

Add node to the resolver list:

// .eslintrc.js
module.exports = {
  settings: {
    'import/resolver': {
      node: {
        extensions: ['.js', '.jsx', '.ts', '.tsx'],
        moduleDirectory: ['node_modules', 'src'],
      },
      typescript: {
        alwaysTryTypes: true,
      },
    },
  },
};

For packages with non-standard exports fields (ESM-only packages):

Some modern packages use the exports field in package.json instead of main. ESLint’s resolver may not support this:

settings: {
  'import/resolver': {
    typescript: {
      alwaysTryTypes: true,
      extensionAlias: {
        '.js': ['.ts', '.tsx', '.js', '.jsx'],
      },
    },
  },
},

Add to import/ignore for packages you cannot resolve:

rules: {
  'import/no-unresolved': ['error', {
    ignore: [
      '^virtual:', // Vite virtual modules
      '^@astro/',  // Astro built-in modules
    ],
  }],
},

Fix 5: Fix for Specific Frameworks

Next.js — aliases and built-in modules:

npm install --save-dev eslint-import-resolver-typescript
// .eslintrc.js
module.exports = {
  extends: ['next/core-web-vitals'],
  settings: {
    'import/resolver': {
      typescript: {
        alwaysTryTypes: true,
        project: './tsconfig.json',
      },
    },
  },
};

Next.js’s @/ alias is defined in tsconfig.json by default when created with create-next-app — the resolver picks it up automatically.

Vite + React — handle Vite-specific virtual modules:

// .eslintrc.js
module.exports = {
  settings: {
    'import/resolver': {
      typescript: { alwaysTryTypes: true },
    },
  },
  rules: {
    'import/no-unresolved': ['error', {
      ignore: ['^virtual:'],  // Vite virtual modules like 'virtual:pwa-register'
    }],
  },
};

React Native — resolve .ios.js and .android.js extensions:

settings: {
  'import/resolver': {
    node: {
      extensions: [
        '.ios.js', '.android.js',
        '.ios.ts', '.android.ts',
        '.js', '.jsx', '.ts', '.tsx',
      ],
    },
  },
},

Fix 6: Disable the Rule for Specific Lines or Files

If you have a module that legitimately cannot be resolved by ESLint (e.g., environment-specific virtual module):

// Disable for a single line
import styles from './app.module.css'; // eslint-disable-line import/no-unresolved

// Disable for entire file
/* eslint-disable import/no-unresolved */
import { plugin } from 'some-virtual-module';
/* eslint-enable import/no-unresolved */

Disable in .eslintrc for a path pattern:

module.exports = {
  overrides: [
    {
      files: ['*.stories.tsx', '*.test.tsx'],
      rules: {
        'import/no-unresolved': 'off', // Test files can import test utilities freely
      },
    },
  ],
};

Fix 7: Turn Off import/no-unresolved When Using TypeScript

If you use TypeScript with @typescript-eslint, TypeScript already catches invalid imports. The import/no-unresolved rule becomes redundant and often causes false positives:

// .eslintrc.js — valid approach for TypeScript projects
module.exports = {
  extends: [
    '@typescript-eslint/recommended',
    'plugin:import/recommended',
    'plugin:import/typescript',
  ],
  rules: {
    // TypeScript handles module resolution — disable the duplicate ESLint check
    'import/no-unresolved': 'off',

    // Keep other import rules that TypeScript doesn't cover
    'import/order': ['warn', { alphabetize: { order: 'asc' } }],
    'import/no-duplicates': 'error',
    'import/no-cycle': 'warn',
  },
};

When to disable vs. configure: If your project uses TypeScript strictly (strict: true, no any), disabling import/no-unresolved is reasonable — TypeScript’s moduleResolution catches bad imports at compile time. If you have loose TypeScript settings or JavaScript files, keep the rule and configure the resolver properly.

Still Not Working?

Restart the ESLint language server. ESLint servers in VS Code cache configuration. After changing .eslintrc.js:

  • VS Code: Ctrl+Shift+P → “ESLint: Restart ESLint Server”
  • Or close and reopen the file

Check ESLint version compatibility. eslint-plugin-import v2 works with ESLint v8. For ESLint v9 (flat config), use eslint-plugin-import v2.29+ or the fork eslint-plugin-import-x:

npx eslint --version
npm list eslint-plugin-import

Run ESLint from the command line to see the actual error:

npx eslint src/App.tsx --rule '{"import/no-unresolved": "error"}' --debug 2>&1 | grep -i "resolve\|alias"

Check for missing @types packages:

# If you get import/no-unresolved for a JS package
npm install --save-dev @types/package-name

For related ESLint and TypeScript tooling issues, see Fix: ESLint Parsing Error Unexpected Token and Fix: TypeScript Cannot Find Module.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles