Vitest "Failed to load url" (unresolved alias) in CI
Vitest tried to load a module by URL and failed because the import specifier (often an @/... alias) never mapped to a real file. Vitest does not read your tsconfig paths automatically.
What this error means
Tests that import via an alias fail at collection with "Failed to load url @/foo (resolved id: @/foo)". The same import works in the app build because Vite/tsconfig resolves it there.
Error: Failed to load url @/lib/client (resolved id: @/lib/client)
in /work/repo/src/app.test.ts. Does the file exist?Common causes
No resolve.alias in the Vitest config
The app relies on a Vite plugin or tsconfig paths that the standalone Vitest config does not inherit, so @/... stays unresolved.
tsconfig paths not bridged to Vitest
TypeScript resolves the alias for type-checking, but Vitest needs an actual runtime resolver entry to load the module.
How to fix it
Declare resolve.alias explicitly
Map the alias prefix to an absolute directory so Vitest can resolve imports the same way the app does.
import { fileURLToPath } from 'node:url';
export default {
resolve: {
alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) },
},
};Bridge tsconfig paths with a plugin
Use vite-tsconfig-paths so a single source of truth (tsconfig) feeds both the build and the tests.
import tsconfigPaths from 'vite-tsconfig-paths';
export default { plugins: [tsconfigPaths()] };How to prevent it
- Keep one resolver source (tsconfig paths) and bridge it into Vitest.
- Add a smoke test that imports through each alias prefix.
- Run the test suite on a clean checkout in CI before merging alias changes.