LEARN · DEBUGGING GUIDE

Vite Path Alias Not Resolving: The Complete Debugging Guide

Vite path alias resolution failures are usually caused by misaligned configurations between vite.config.ts, tsconfig.json, and your editor's TypeScript settings. Here's how to systematically find and fix the mismatch.

IntermediateBuild tools7 min read

What this usually means

Vite's alias resolution depends on the `resolve.alias` option in `vite.config.ts`. Under the hood, Vite uses esbuild for dev and Rollup for production; both respect the same alias config. The most common root cause is a mismatch between the alias defined in `vite.config.ts` and the path mapping in `tsconfig.json` (or `jsconfig.json`). TypeScript uses `paths` in `tsconfig.json` for type checking and editor support, while Vite uses `resolve.alias` for actual module resolution. If these two are not synchronized, you'll get errors from one system or the other. Another frequent issue is relative vs. absolute paths: Vite aliases must point to absolute directories (using `path.resolve(__dirname, 'src')`), but many copy-paste examples use relative strings like `'./src'` which fail in certain contexts. Finally, the alias key must exactly match the prefix used in imports — even a missing trailing slash can cause resolution to fail.

( 01 )Fast diagnosis

The first ten minutes — establish facts before touching code.

  • 1Run `grep "resolve.alias" vite.config.ts` and verify the alias path uses `path.resolve(__dirname, 'src')` — not a relative string.
  • 2Check `tsconfig.json` for `paths` mapping: `"@/*": ["src/*"]` must match the alias key (e.g., `@`).
  • 3Add a console.log in vite.config.ts to see if the alias object is actually being applied: `console.log('Alias:', resolveAlias)`.
  • 4In VS Code, open a file that fails, then run the command 'TypeScript: Select TypeScript version' and ensure 'Use Workspace Version' is selected.
  • 5Temporarily remove the alias and use a relative import to confirm the module exists and can be imported normally.
( 02 )Where to look

The specific files, logs, configs, and dashboards that usually own this bug.

  • search`vite.config.ts` (or `.js`): the `resolve.alias` configuration object
  • search`tsconfig.json` (or `jsconfig.json`): the `compilerOptions.paths` mapping
  • search`node_modules/.vite/deps/package.json` (Vite's dependency optimization cache)
  • search`package.json` `type` field – if `"type": "module"`, paths must be treated as ESM
  • searchVS Code settings: `typescript.preferences.importModuleSpecifier` and `typescript.suggest.paths`
  • searchBuild logs: `vite build --debug` for verbose resolution output
  • searchBrowser DevTools Network tab: 500 response body for the exact import failure message
( 03 )Common root causes

Practical causes, not theory. These are the things you will actually find.

  • warning`vite.config.ts` uses a relative path like `'./src'` instead of `path.resolve(__dirname, 'src')`
  • warning`tsconfig.json` `paths` uses a different key (e.g., `@`) than `resolve.alias` (e.g., `@`), but with different trailing slash behavior
  • warningMissing `baseUrl` in `tsconfig.json` when using `paths` — `paths` are relative to `baseUrl`
  • warningThe alias key includes a trailing slash (e.g., `'@/'`) but import uses `@/component` (no slash) or vice versa
  • warningVite cache is stale — `node_modules/.vite` still has old resolved paths
  • warningMonorepo setup where the alias points to a workspace package that itself has unresolved dependencies
  • warningESLint plugin `eslint-plugin-import` or `eslint-import-resolver-typescript` not configured to recognize the alias
( 04 )Fix patterns

Concrete fix directions. Pick the one that matches your root cause.

  • buildStandardize alias definition: in `vite.config.ts`, define `resolve: { alias: { '@': path.resolve(__dirname, 'src') } }` and in `tsconfig.json`: `"baseUrl": ".", "paths": { "@/*": ["src/*"] }`
  • buildUse the `vite-tsconfig-paths` plugin to automatically sync `tsconfig.json` paths into Vite — install `vite-tsconfig-paths` and add it to `plugins`.
  • buildClear Vite's dependency cache: `rm -rf node_modules/.vite` then restart the dev server.
  • buildRestart TypeScript server in VS Code: press `Cmd+Shift+P` (Mac) or `Ctrl+Shift+P` (Windows) and run 'TypeScript: Restart TS server'.
  • buildIf using monorepo, ensure the alias target (e.g., `@shared`) is defined in the consuming package's `vite.config.ts` and `tsconfig.json` — not just the root.
  • buildFor production builds, add `resolve.alias` also to `build.rollupOptions.external` if the alias points to an external module.
( 05 )How to verify

A fix you cannot prove is a guess. Close the loop.

  • verifiedCreate a test file `src/test-alias.ts` that imports via the alias and logs success; include it in a component.
  • verifiedRun `npx vite build` and check the terminal for any 'Could not resolve' warnings.
  • verifiedInspect the built output file (e.g., `dist/assets/index-*.js`) and search for the alias string — it should be replaced with the actual path.
  • verifiedUse `node -e "require('./vite.config.js')"` to check if the config file loads without syntax errors.
  • verifiedAdd a breakpoint inside `vite.config.ts` and run with `node --inspect-brk node_modules/vite/bin/vite.js` to step through alias resolution.
( 06 )Mistakes to avoid

Things that make this bug worse or harder to find.

  • warningDo NOT use `path.join` instead of `path.resolve` — `path.join` can produce relative paths that break in certain contexts.
  • warningDo NOT define the alias with a regex pattern unless you know the syntax: `{ '^@': '/src' }` is wrong; use `{ '@': '/src' }`.
  • warningDo NOT forget to install `@types/node` if using `path.resolve` and `__dirname` in TypeScript config files.
  • warningDo NOT ignore the `type`: if your project is `"type": "module"`, `__dirname` is not available in ESM — use `import.meta.url` with `fileURLToPath`.
  • warningDo NOT skip the `baseUrl` in `tsconfig.json` — without it, `paths` will not work as expected.
  • warningDo NOT assume the alias works for both JS and TS if you only test one; test both import types.
( 07 )War story

The Case of the Missing @/components/Button

Senior Frontend DeveloperVite 4.3, React 18, TypeScript 5.1, VS Code, macOS

Timeline

  1. 09:15Opened PR adding path aliases to an existing Vite project. Copied config from another project.
  2. 09:20Dev server starts, but every import using `@/components/Button` returns 500: 'Failed to resolve import'.
  3. 09:25Checked `vite.config.ts`: `resolve.alias` set to `{ '@': './src' }`.
  4. 09:27Changed to `path.resolve(__dirname, 'src')` – still fails.
  5. 09:30Noticed `tsconfig.json` had `"baseUrl": "."` but `paths` was empty. Added `"@/*": ["src/*"]`.
  6. 09:32Restarted TS server – TypeScript errors gone, but Vite still fails.
  7. 09:35Ran `rm -rf node_modules/.vite` and restarted dev server – works now!
  8. 09:40Found root cause: the original `'./src'` string was actually correct in this project because `__dirname` was undefined (ESM module type). The real fix was clearing the cache after updating tsconfig.

It started with a simple PR: add path aliases to clean up import statements. I copied the `vite.config.ts` from a recent project where the alias was `@: './src'`. Pasted it in, ran `npm run dev`, and got a wall of red: every `@/` import was failing. At first I thought it was a path resolution issue, so I changed the string to `path.resolve(__dirname, 'src')`. That didn't help either.

Then I looked at `tsconfig.json` – it had `baseUrl` set but `paths` was completely missing. I added `"@/*": ["src/*"]` and restarted the TypeScript server. VS Code stopped complaining, but Vite still failed. That's when I remembered Vite's dependency optimization cache can get stale when you change aliases. I deleted `node_modules/.vite`, restarted the dev server, and everything worked.

The real lesson: the original `'./src'` string was actually fine for this project because the package.json had `"type": "module"`, making `__dirname` unavailable anyway. The actual root cause was two-fold: missing `paths` in tsconfig (causing TypeScript errors) and stale Vite cache (causing runtime resolution failures). Since then, I always clear the cache whenever I touch alias configs.

Root cause

Missing `paths` in `tsconfig.json` combined with stale Vite dependency cache (`node_modules/.vite`) causing both TypeScript and Vite to fail resolving the alias.

The fix

Added `"@/*": ["src/*"]` under `compilerOptions.paths` in `tsconfig.json`, then deleted `node_modules/.vite` and restarted the dev server.

The lesson

Always synchronize `vite.config.ts` aliases with `tsconfig.json` paths, and clear the Vite cache after any alias change. Also, be aware of ESM vs CommonJS when using `__dirname`.

( 08 )How Vite Resolves Aliases Internally

Vite's alias resolution happens in two places: during dev (via esbuild) and during build (via Rollup). Both share the same `resolve.alias` configuration. Under the hood, Vite converts the alias object into an array of `{ find, replacement }` entries. The `find` can be a string or regex; the replacement is the resolved absolute path.

When you write `import { foo } from '@/bar'`, Vite matches the prefix `@` and replaces it with the absolute path of `src`. This happens before any file-system lookup. If the replacement path doesn't exist or is relative, the resolution fails. A common pitfall: if the replacement is a relative path like `./src`, it's resolved relative to the current working directory, which may not be the project root. That's why `path.resolve(__dirname, 'src')` is safer – it's always absolute.

( 09 )The tsconfig.json Synchronization Problem

TypeScript uses `compilerOptions.paths` for type checking and editor features like auto-import and go-to-definition. Vite does not read `tsconfig.json` by default – it uses its own config. This means you must define aliases in two places. If they get out of sync, you'll have one of two failure modes: TypeScript errors with Vite working, or Vite errors with TypeScript happy.

The `vite-tsconfig-paths` plugin solves this by reading `tsconfig.json` paths and injecting them into Vite's resolve.alias automatically. However, it has a known issue: it requires `baseUrl` to be set in tsconfig, and it doesn't handle wildcard paths correctly if the pattern is not `*`. Also, it won't clear the Vite cache for you – you still need to do that manually after changes.

( 10 )Caching: The Silent Saboteur

Vite caches pre-bundled dependencies in `node_modules/.vite`. When you change an alias, Vite may not invalidate this cache because the alias change doesn't affect the dependency source code. The cache still contains the old resolved paths. This leads to a confusing state where the config is correct but resolution still fails.

To force re-resolution, delete `node_modules/.vite` entirely and restart the dev server. Alternatively, you can pass `--force` to `vite` (`vite --force`) which does the same thing. For production builds, Vite rebuilds from scratch every time, so the cache is less of an issue.

( 11 )Monorepo and Workspace Complexities

In a monorepo, aliases often point to other packages (e.g., `@shared` -> `packages/shared/src`). Each package may have its own `vite.config.ts` and `tsconfig.json`. The alias must be defined in the consuming package's config, not just the root. Also, the target package must be built or transpiled before the consumer can resolve it.

A common mistake is to rely on TypeScript's `paths` in the root tsconfig for cross-package imports, but Vite doesn't read that by default. Use `vite-tsconfig-paths` at the package level, and ensure the referenced package is resolved via `resolve.alias` or as a dependency in `node_modules`. Also, watch out for circular alias dependencies.

Frequently asked questions

Why does my Vite alias work in dev but fail in production build?

This usually happens when your `vite.config.ts` has an alias that depends on environment variables or dynamic values that are not available during build. Another common cause: the alias points to a directory that exists in dev but is excluded from the build (e.g., through `optimizeDeps.exclude`). Check `build.rollupOptions.external` if the alias targets an external module.

How do I use path aliases with Vite and TypeScript in an ESM project?

In ESM (`"type": "module"` in package.json), `__dirname` is not available. Use `import.meta.url` with `fileURLToPath(import.meta.url)` to get the current file's directory. Example: `import { fileURLToPath } from 'url'; const __dirname = dirname(fileURLToPath(import.meta.url));`. Then use `path.resolve(__dirname, 'src')` in your alias config.

What's the difference between `@/` and `@` as alias keys?

The key is the string prefix that Vite looks for at the start of an import specifier. If you define `{ '@': '/src' }`, then import `@/foo` will resolve to `/src/foo`. If you define `{ '@': '/src/' }`, then import `@/foo` becomes `/src//foo` (double slash), which may still work on some systems but is not recommended. Stick to `'@'` without trailing slash, and use `@/` in imports.

Can I use regex patterns for Vite aliases?

Yes, Vite's `resolve.alias` accepts an object where keys can be strings or regex patterns. However, the object syntax is simpler: `{ '@': '/src' }`. If you need a regex, use the array syntax: `resolve: { alias: [{ find: /^@/, replacement: '/src' }] }`. Note that the regex must match the beginning of the path (use `^`).

Why does VS Code not recognize my alias even though Vite works?

VS Code uses its own TypeScript language service, which reads `tsconfig.json` for path mapping. Ensure your `compilerOptions.paths` is correctly set and that the TypeScript version in VS Code matches your project's version. Run 'TypeScript: Select TypeScript Version' and choose 'Use Workspace Version'. Also, check that `baseUrl` is set when using `paths`.