Fix: [vite] Internal server error: Failed to resolve import

The Error

You start your Vite dev server and the browser shows a full-screen error overlay:

Missing or unresolvable import:

[vite] Internal server error: Failed to resolve import "some-package" from "src/App.tsx". Does the file exist?

With a relative path:

[vite] Internal server error: Failed to resolve import "./components/Header" from "src/App.tsx". Does the file exist?

Pre-transform variant (appears in the terminal):

Pre-transform error: Failed to resolve import "some-package" from "src/main.ts". Does the file exist?

CJS dependency that wasn’t pre-bundled:

Optimized dependency some-cjs-package needs to be force included

Stale dependency optimization:

504 (Outdated Optimize Dep)

All of these stem from the same root problem: Vite’s dev server serves your source files as native ES modules over HTTP. When the browser requests a module and Vite can’t resolve one of its imports, it has no fallback. The import fails and Vite reports the error.

Why This Happens

Vite’s dev server works fundamentally differently from Webpack. Instead of bundling everything upfront, Vite serves each file individually as an ES module. The browser sends HTTP requests for each import, and Vite resolves and transforms them on the fly.

This architecture is what makes Vite fast, but it also means every import must resolve at request time. There’s no bundling step to catch issues before the browser sees them.

The most common causes:

  • The package isn’t installed. You added an import but never ran npm install.
  • The import path is wrong. A typo, a missing file extension, wrong casing, or a path alias that Vite doesn’t know about.
  • A CJS-only package wasn’t pre-bundled. Vite pre-bundles dependencies using esbuild to convert CommonJS to ESM. If a dependency is missed during this step, the browser can’t load it.
  • Vite’s dependency cache is stale. After installing or updating packages, Vite’s cached pre-bundled dependencies in node_modules/.vite can get out of sync.
  • A path alias is configured in TypeScript but not in Vite. tsconfig.json paths don’t automatically work in Vite. You need resolve.alias in vite.config too.

Fix 1: Install the Missing Package

If the error names an npm package (not a relative path), install it:

npm install some-package

If you just cloned a repo or switched branches, install all dependencies:

npm install

After installing, restart the Vite dev server. Vite needs to detect the new dependency and pre-bundle it. If npm install fails with peer dependency conflicts, see Fix: npm ERR! ERESOLVE unable to resolve dependency tree.

Note: Unlike Webpack, Vite doesn’t always give you a clear “module not found” message for missing packages. Sometimes the error says “Failed to resolve import” with no hint that the package simply isn’t installed. Always check node_modules first.

Fix 2: Fix the Import Path

If the error points to a relative import, the file path is wrong.

Check for typos and casing

Vite resolves paths using the actual file system. On Linux (and in most CI environments), paths are case-sensitive:

// File on disk: src/components/Header.tsx

// Works on macOS/Windows, fails on Linux
import Header from './components/header';

// Correct -- matches exact file name
import Header from './components/Header';

This is a common source of “works locally, fails in CI” bugs. Always match the exact casing of the file and directory names.

File extensions

Vite resolves these extensions automatically by default: .mjs, .js, .mts, .ts, .jsx, .tsx, .json. You can omit them in imports:

// Both work
import Header from './components/Header';
import Header from './components/Header.tsx';

If you’re importing a file type not in that list (like .vue, .svelte, or .css), you must include the extension. You can also extend the list using resolve.extensions in vite.config:

// vite.config.js
export default {
  resolve: {
    extensions: ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json', '.vue']
  }
}

Index files

Vite resolves index files when you import a directory:

// Resolves to ./components/index.ts (or .tsx, .js, etc.)
import { Button } from './components';

If the directory doesn’t have an index file, you’ll get “Failed to resolve import.” Either create an index file or import the specific file:

import { Button } from './components/Button';

Fix 3: Configure resolve.alias in Vite Config

If you use path aliases like @/components/Header, Vite needs to know how to resolve them. TypeScript’s tsconfig.json paths alone are not enough — Vite doesn’t read them by default.

Add resolve.alias in your Vite config:

// vite.config.ts
import { defineConfig } from 'vite';
import path from 'path';

export default defineConfig({
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
});

Now import Header from '@/components/Header' resolves to src/components/Header.

Using vite-tsconfig-paths instead

If you already have aliases defined in tsconfig.json and don’t want to duplicate them, use the vite-tsconfig-paths plugin:

npm install --save-dev vite-tsconfig-paths
// vite.config.ts
import { defineConfig } from 'vite';
import tsconfigPaths from 'vite-tsconfig-paths';

export default defineConfig({
  plugins: [tsconfigPaths()],
});

This reads your tsconfig.json paths and applies them as Vite aliases automatically. One source of truth, no duplication.

Multiple aliases

For projects with multiple path aliases:

// vite.config.ts
import { defineConfig } from 'vite';
import path from 'path';

export default defineConfig({
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
      '@components': path.resolve(__dirname, './src/components'),
      '@utils': path.resolve(__dirname, './src/utils'),
      '@assets': path.resolve(__dirname, './src/assets'),
    },
  },
});

Fix 4: Force Pre-Bundle with optimizeDeps.include

Vite pre-bundles dependencies using esbuild during dev server startup. This converts CommonJS packages to ESM so the browser can load them. But sometimes Vite misses a dependency — especially ones that are dynamically imported, conditionally required, or buried deep in another package’s dependency tree.

When this happens, you see errors like:

Optimized dependency some-cjs-package needs to be force included

Or the import simply fails because the browser receives a CommonJS module.exports that it can’t parse as ESM.

Force Vite to pre-bundle the problematic dependency:

// vite.config.ts
export default defineConfig({
  optimizeDeps: {
    include: ['some-cjs-package'],
  },
});

When to use optimizeDeps.include

  • CJS-only packages that Vite’s auto-detection missed.
  • Packages imported dynamically with import() — Vite only scans static imports during its initial crawl.
  • Deeply nested dependencies that another package require()s internally but aren’t in your own package.json.
  • Packages that re-export from many files and cause a waterfall of HTTP requests (hundreds of modules loading individually). Pre-bundling collapses them into one file.

Linked dependencies in monorepos

If you’re linking a package with npm link, yarn link, or workspace protocols, Vite doesn’t pre-bundle linked dependencies by default. If the linked package is CommonJS or has CJS dependencies of its own, force-include it:

// vite.config.ts
export default defineConfig({
  optimizeDeps: {
    include: ['my-linked-package'],
  },
});

You may also need to add it to resolve.dedupe to prevent duplicate copies of shared dependencies like React:

// vite.config.ts
export default defineConfig({
  resolve: {
    dedupe: ['react', 'react-dom'],
  },
  optimizeDeps: {
    include: ['my-linked-package'],
  },
});

Fix 5: Clear Vite’s Dependency Cache

Vite caches pre-bundled dependencies in node_modules/.vite. When you install, update, or remove packages, this cache can become stale. Symptoms include the 504 (Outdated Optimize Dep) error or imports that worked before suddenly breaking.

Delete the cache and restart the dev server:

rm -rf node_modules/.vite
npm run dev

Or use Vite’s built-in flag to force a fresh optimization:

npx vite --force

The --force flag tells Vite to ignore its cache and re-run dependency optimization from scratch.

When stale cache causes problems

  • After running npm install or npm update — the cached bundles may reference old versions.
  • After switching git branches — a different branch might have different dependency versions.
  • After modifying vite.config — changes to optimizeDeps, resolve.alias, or plugins can invalidate the cache.
  • The 504 (Outdated Optimize Dep) error — this specifically means the browser is requesting a pre-bundled dependency that Vite has since re-optimized. A hard refresh (Ctrl+Shift+R) usually fixes it. If it persists, clear the cache.

Edge Cases

Monorepo linked packages

In a monorepo (npm workspaces, Yarn workspaces, pnpm workspaces), packages that reference sibling packages can fail to resolve in Vite. Common issues:

Sibling package not found:

Make sure the sibling package is listed as a dependency in package.json with the workspace protocol:

{
  "dependencies": {
    "@myorg/shared": "workspace:*"
  }
}

Sibling package’s dependencies not pre-bundled:

Vite doesn’t crawl through linked workspace packages to pre-bundle their dependencies. If @myorg/shared depends on a CJS package, you need to include it explicitly:

// vite.config.ts
export default defineConfig({
  optimizeDeps: {
    include: ['@myorg/shared > lodash'],
  },
});

The > syntax tells Vite to pre-bundle lodash as it’s imported by @myorg/shared.

CJS-only packages

Some older packages only ship CommonJS. Vite’s dev server needs ESM. While Vite auto-detects most CJS dependencies and pre-bundles them, some slip through — particularly packages that:

  • Use require() with dynamic expressions (require('./locale/' + lang))
  • Have a module field in package.json that points to a broken ESM build
  • Export using module.exports mixed with __esModule flags in unusual ways

For these packages, optimizeDeps.include is the fix. If that still doesn’t work, check if the package has a newer version with ESM support, or look for an alternative package.

SSR externals

If you’re using Vite for server-side rendering (SSR), Vite externalizes dependencies by default during SSR builds — it doesn’t bundle them, expecting Node.js to resolve them at runtime. This can cause “Failed to resolve import” during SSR if:

  • The package uses browser-specific imports that Node.js can’t resolve.
  • The package’s exports field doesn’t include a Node.js/CJS entry point.

Force Vite to bundle the package during SSR:

// vite.config.ts
export default defineConfig({
  ssr: {
    noExternal: ['problematic-package'],
  },
});

Or, in newer versions of Vite (5.1+):

// vite.config.ts
export default defineConfig({
  ssr: {
    noExternal: true, // Bundle everything (use with caution)
  },
});

exports field blocking deep imports

Some packages use the exports field in their package.json to restrict which files can be imported. If you’re trying to import a subpath that isn’t listed in exports, Vite will fail to resolve it:

// Fails if "some-package" doesn't export "./internals" in its exports map
import { helper } from 'some-package/internals';

There’s no clean workaround. Either import from an exported path, or ask the package maintainer to add the subpath to exports. As a last resort, you can reference the file directly through node_modules, but this is fragile:

import { helper } from 'some-package/dist/internals.js';

Still Not Working?

Vite vs. Webpack: resolution differences

If you’re migrating from Webpack (e.g., from Create React App), be aware of key differences:

  • Webpack resolves src/ imports without ./. In CRA, import Header from 'components/Header' works because src/ is in resolve.modules. Vite doesn’t do this. You need ./components/Header or a path alias.
  • Webpack’s resolve.extensions includes more types by default. Vite’s default list doesn’t include .vue or .svelte — add them in config if needed.
  • Webpack bundles everything in dev. Vite doesn’t. Packages that rely on Webpack-specific behavior (like require.context) won’t work.
  • Environment variables use a different prefix. Webpack uses process.env.REACT_APP_*. Vite uses import.meta.env.VITE_*. Imports that reference process.env will fail in Vite unless you define them. See Fix: process.env.VARIABLE_NAME is undefined for details on framework-specific prefixes.

HMR issues after fixing the import

Sometimes you fix the import path but Vite’s HMR (Hot Module Replacement) doesn’t pick up the change. The error overlay stays visible even though the code is correct. Force a full reload:

  1. Save the file again to trigger HMR.
  2. Hard refresh the browser (Ctrl+Shift+R / Cmd+Shift+R).
  3. If that doesn’t work, restart the dev server.

If HMR consistently fails to recover after fixing errors, clear the Vite cache:

rm -rf node_modules/.vite && npx vite

TypeScript path aliases need both tsconfig.json and vite.config

This is worth repeating because it catches many developers. TypeScript path aliases (paths in tsconfig.json) only affect type checking. They tell the TypeScript compiler how to resolve types, but they don’t tell Vite how to resolve the actual files.

You need both:

tsconfig.json (for TypeScript/IDE type resolution):

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  }
}

vite.config.ts (for runtime/build resolution):

import { defineConfig } from 'vite';
import path from 'path';

export default defineConfig({
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
});

Or use vite-tsconfig-paths to avoid the duplication (see Fix 3).

Check the full error stack in the terminal

The browser overlay shows a simplified error. The terminal where Vite is running often shows more detail — including the full resolution path Vite tried, which dependencies it scanned, and the exact point where resolution failed. Always check the terminal output when debugging import errors.


Related: If you’re getting Cannot find module errors in Node.js (outside of Vite), see Fix: Error Cannot find module.

Related Articles

Fix: Module not found: Can't resolve / Cannot find module or its corresponding type declarations

How to fix 'Module not found: Can't resolve' in webpack, Vite, and React, and 'Cannot find module or its corresponding type declarations' in TypeScript. Covers missing packages, wrong import paths, case sensitivity, path aliases, node_modules corruption, monorepo hoisting, barrel files, and asset imports.

Fix: TS2322 Type 'X' is not assignable to type 'Y'

How to fix TypeScript error TS2322 'Type is not assignable to type'. Covers literal types vs general types, string vs String, union types, interface compatibility, generic constraints, readonly arrays, excess property checking, discriminated unions, type assertions, type widening and narrowing, React event handlers, Promise return types, and enum mismatches.

Fix: Loading chunk failed / ChunkLoadError

How to fix 'Loading chunk failed', 'ChunkLoadError', and 'Failed to fetch dynamically imported module' in webpack, Next.js, React, and Vite. Covers stale deployments, CDN caching, publicPath misconfiguration, service worker cache, code splitting, dynamic import retry strategies, React.lazy error boundaries, and Next.js-specific solutions.

Fix: TS2532 Object is possibly 'undefined' / Object is possibly 'null'

How to fix TypeScript errors TS2532 'Object is possibly undefined', TS18048 'Object is possibly undefined', and 'Object is possibly null'. Covers optional chaining, nullish coalescing, type narrowing, non-null assertion, type guards, strictNullChecks, Array.find, Map.get, React useRef, and more.