886 words
4 minutes

Vite “Failed to Resolve Import” Alias: Align tsconfig, Vite, and Vitest

2026-08-19
Frontend
Vite
/
JavaScript
/
Frontend
/
Troubleshooting

If Vite reports Failed to resolve import "@/components/Button", but the editor and TypeScript are happy, the usual cause is a split alias configuration. TypeScript’s paths setting helps TypeScript resolve source files; it does not rewrite imports or configure Vite’s runtime resolver.

Define the alias in Vite with an absolute filesystem path, or enable Vite’s resolve.tsconfigPaths option when your installed Vite version supports it. If tests use a separate Vitest configuration, make sure that configuration sees the same mapping.

The short fix: give Vite an absolute alias#

For a project where @ should point to src, add an alias to vite.config.ts:

import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
export default defineConfig({
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
})

Then this import has one clear Vite-side meaning:

import Button from '@/components/Button.vue'

The Vite resolve.alias documentation recommends absolute filesystem paths for filesystem aliases. A relative value such as '@': './src' is not equivalent: Vite passes relative replacement values through as-is, which can make resolution depend on the importing file or tool.

Keep the TypeScript mapping too, so the editor, type checker, and Vite agree:

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

The exact baseUrl and paths shape may differ in a monorepo, but the important boundary is the same: TypeScript and Vite each need a resolver that understands the alias.

Why tsconfig.json alone is not enough#

TypeScript describes paths as a way to tell the compiler where a module name may be found. The setting does not change the import text emitted by TypeScript, and another tool must implement the runtime or bundler mapping.

That distinction explains a common sequence:

  1. The editor can jump to @/lib/api.
  2. tsc --noEmit passes.
  3. Vite fails while scanning the module graph.

Nothing is contradictory here. The first two checks use TypeScript’s resolver; the third uses Vite’s resolver. Configure both or choose a supported bridge between them.

Choose one Vite-side mapping strategy#

An explicit resolve.alias is the most portable option. It is easy to inspect, works without a plugin, and makes the filesystem target obvious.

Current Vite documentation also exposes resolve.tsconfigPaths. When enabled, Vite reads paths from the relevant TypeScript configuration:

import { defineConfig } from 'vite'
export default defineConfig({
resolve: {
tsconfigPaths: true,
},
})

Use this only when the option exists in the Vite version used by the project and the selected tsconfig describes the imports you actually build. It is not a reason to leave a stale or ambiguous paths configuration in place. Packages with non-JavaScript extensions may need explicit inclusion, and a monorepo may require a deliberate project root.

Vitest uses Vite’s resolution model but also has an alias test option. If vitest.config.ts is separate from vite.config.ts, confirm that the test configuration inherits the Vite config or repeats the alias intentionally. A passing Vite dev server does not prove that a test runner started with a different root or config file can resolve the same import.

Debug the resolver boundary, not the browser#

When the alias still fails, check the smallest set of facts that changes resolution:

  • Path case: @/Components/Button and @/components/Button are different on case-sensitive filesystems.
  • Real file location: confirm the target exists under the directory the alias points to.
  • Project root: run Vite from the package whose config and tsconfig.json you inspected, especially in a workspace.
  • Config file: make sure the command is loading the intended vite.config.ts or vitest.config.ts.
  • Alias order: Vite resolves aliases in order, so overlapping entries can capture a more specific import unexpectedly.
  • Import kind: a JavaScript import, CSS url(), asset URL, and Node-side require() do not all use the same resolution path.

Use the failing command directly so its config is visible:

Terminal window
pnpm exec vite --debug
pnpm exec vitest --run

If the message is wrapped in Vite’s optimized-dependency error, the Vite cache troubleshooting guide explains when vite --force is useful. Rebuilding that cache cannot repair a missing alias or a file with the wrong case. Likewise, if the problem appears only after deployment, compare it with the Vite production environment guide instead of changing the dev resolver.

Verify the same import in every tool#

After changing the mapping, test the exact boundary that failed:

Terminal window
pnpm exec vite --host 127.0.0.1
pnpm exec vitest --run
pnpm build

Open a route that imports the aliased module, run one test that imports the same path, and complete a production build. The fix is complete only when the editor, test runner, dev server, and build all agree on the target.

The durable rule is simple: tsconfig.paths describes TypeScript lookup, while Vite and Vitest need their own resolver behavior. Keep one authoritative target, use an absolute filesystem alias when configuring it explicitly, and verify from the package root that actually runs the command.

FAQ#

Q: Why does tsc pass while Vite cannot resolve the import?#

A: TypeScript’s paths setting informs TypeScript but does not configure Vite. Add resolve.alias, enable a supported resolve.tsconfigPaths option, or use another resolver integration that Vite loads.

Q: Should I use @ or @/* in the alias?#

A: In Vite, map the bare prefix such as '@' to an absolute src directory. In TypeScript, map the import pattern such as '@/*' to src/*. The two syntaxes serve different configuration formats.

Q: Do I need a separate alias for Vitest?#

A: Not always. Vitest can inherit Vite’s configuration, and its alias option is merged with Vite aliases. If the test command loads a separate config or uses another root, make that inheritance or mapping explicit and test the import directly.

References:

Vite Shared Options: resolve.alias and resolve.tsconfigPaths

Vitest Configuration: alias

TypeScript TSConfig: paths

Vite “Failed to Resolve Import” Alias: Align tsconfig, Vite, and Vitest
https://laplusda.com/en/posts/vite-resolve-alias-import-error/
Author
Zero
Published at
2026-08-19
License
CC BY-NC-SA 4.0
Was this article useful?

Report a typo or broken link, or suggest a related topic.