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 includedStale 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/.vitecan get out of sync. - A path alias is configured in TypeScript but not in Vite.
tsconfig.jsonpaths don’t automatically work in Vite. You needresolve.aliasinvite.configtoo.
Fix 1: Install the Missing Package
If the error names an npm package (not a relative path), install it:
npm install some-packageIf you just cloned a repo or switched branches, install all dependencies:
npm installAfter 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 includedOr 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 ownpackage.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 devOr use Vite’s built-in flag to force a fresh optimization:
npx vite --forceThe --force flag tells Vite to ignore its cache and re-run dependency optimization from scratch.
When stale cache causes problems
- After running
npm installornpm 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 tooptimizeDeps,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
modulefield inpackage.jsonthat points to a broken ESM build - Export using
module.exportsmixed with__esModuleflags 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
exportsfield 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 becausesrc/is inresolve.modules. Vite doesn’t do this. You need./components/Headeror a path alias. - Webpack’s
resolve.extensionsincludes more types by default. Vite’s default list doesn’t include.vueor.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 usesimport.meta.env.VITE_*. Imports that referenceprocess.envwill 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:
- Save the file again to trigger HMR.
- Hard refresh the browser (
Ctrl+Shift+R/Cmd+Shift+R). - 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 viteTypeScript 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.