Skip to content
Latchkey

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.

node
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.

vitest.config.ts
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.

vitest.config.ts
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.

Related guides

Tired of flaky CI? Latchkey auto-heals failed jobs and retries them for you. Start free →