This error occurs when Vite cannot resolve path aliases like @/components because it doesn't automatically recognize TypeScript path mappings. Both vite.config.ts and tsconfig.json need proper configuration.
This error appears when Vite's bundler (Rollup) cannot locate a module using a path alias like `@/components` or `@/utils`. While TypeScript handles type checking and understands paths defined in tsconfig.json, Vite runs its own module resolution system that needs separate configuration. The core issue is that Vite and TypeScript are two independent tools that don't automatically share configuration. TypeScript uses the `paths` option in tsconfig.json for type checking and IDE support, but Vite uses its own `resolve.alias` setting in vite.config.ts for actual module bundling. When only one is configured, you'll get import errors even though your IDE might show everything as valid. This commonly happens in React projects scaffolded with Vite when developers add custom path aliases to tsconfig.json but forget to mirror them in the Vite configuration, or vice versa.
First, confirm your TypeScript configuration includes the path mappings. Open tsconfig.json and ensure you have both baseUrl and paths configured:
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@utils/*": ["src/utils/*"]
}
}
}The baseUrl is required for path resolution to work correctly. Without it, TypeScript won't know where to start resolving your aliases.
Add the resolve.alias configuration to your Vite config file. For projects using ES modules (recommended), use fileURLToPath from node:url:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { fileURLToPath } from 'node:url'
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
'@components': fileURLToPath(new URL('./src/components', import.meta.url)),
'@utils': fileURLToPath(new URL('./src/utils', import.meta.url))
}
}
})Or if using CommonJS:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'@components': path.resolve(__dirname, './src/components')
}
}
})To avoid duplicating configuration, install the vite-tsconfig-paths plugin which automatically reads your tsconfig.json and applies aliases:
npm install -D vite-tsconfig-pathsThen update your vite.config.ts:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tsconfigPaths from 'vite-tsconfig-paths'
export default defineConfig({
plugins: [
react(),
tsconfigPaths()
]
})This approach is cleaner because you maintain path aliases in one place (tsconfig.json) and the plugin synchronizes them to Vite automatically.
Configuration changes to vite.config.ts or tsconfig.json are not hot-reloaded. Stop your dev server (Ctrl+C) and restart it:
npm run devVite only reads configuration files on startup, so changes won't take effect until you restart.
Ensure your import statements match the alias configuration exactly. If you configured @/*, use it with the trailing slash:
// Correct - matches "@/*": ["src/*"]
import Button from '@/components/Button'
// Incorrect - missing the slash after @
import Button from '@components/Button' // unless you defined @components separatelyAlso check for case sensitivity. On Linux and macOS, @/Components/button is different from @/components/Button.
If you're encountering this error in Vitest tests, the test runner needs the same alias configuration. Either use the vite-tsconfig-paths plugin (which works for Vitest too), or add the alias manually to vitest.config.ts:
import { defineConfig } from 'vitest/config'
import { fileURLToPath } from 'node:url'
export default defineConfig({
test: {
// ... other test config
},
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})Why doesn't Vite read tsconfig.json automatically?
Vite prioritizes build speed and keeps its dependency graph minimal. Reading and parsing tsconfig.json during every build would add overhead, and not all Vite projects use TypeScript. The vite-tsconfig-paths plugin bridges this gap when needed.
Non-TypeScript files and path aliases
If you're using path aliases in .vue, .svelte, or .mdx files (non-TypeScript modules), you must set "allowJs": true in tsconfig.json. Otherwise, TypeScript won't process these files and the alias resolution will fail.
Monorepo considerations
In monorepo setups with multiple tsconfig files (like tsconfig.app.json, tsconfig.node.json), ensure the vite-tsconfig-paths plugin points to the correct config file. You can specify it explicitly:
tsconfigPaths({ root: './tsconfig.app.json' })Production build failures
If aliases work in dev but fail during vite build, check for case sensitivity issues. Your development OS (Windows) might be case-insensitive while your CI/production environment (Linux) is case-sensitive. Always match the exact casing of your file and directory names.
React Hook useCallback has a missing dependency: 'variable'. Either include it or remove the dependency array react-hooks/exhaustive-deps
React Hook useCallback has a missing dependency
Cannot use private fields in class components without TS support
Cannot use private fields in class components without TS support
Cannot destructure property 'xxx' of 'undefined'
Cannot destructure property of undefined when accessing props
useNavigate() may be used only in the context of a <Router> component.
useNavigate() may be used only in the context of a Router component
Cannot find module or its corresponding type declarations
How to fix "Cannot find module or type declarations" in Vite