TypeScript path aliases make imports shorter and easier to refactor, but they also create one of the most common setup mismatches in modern projects: the editor understands an alias, while the bundler, test runner, or Node process does not. This guide gives you a reusable checklist for configuring baseUrl and paths across Vite, Next.js, Node, Jest, and ts-node, plus the practical checks to run when import resolution breaks after a tooling change.
Overview
Here is the main rule to keep in mind: TypeScript path aliases are not a single switch. They are a contract that several tools may need to honor separately.
In most projects, the first step happens in tsconfig.json. That makes your editor and the TypeScript compiler aware of imports such as @/components/Button or @server/utils/env. But TypeScript does not automatically teach every other tool how to resolve those imports at runtime.
That is why alias setups often feel inconsistent. You may see one of these patterns:
- The IDE autocomplete works, but the dev server fails.
- The app builds, but tests cannot resolve modules.
tsc --noEmitpasses, but a Node script crashes at runtime.- Next.js works in app code, but a custom Jest config still needs mapping.
A reliable setup usually has four parts:
- TypeScript config defines the alias shape.
- Build or dev tool resolves the alias during bundling.
- Test runner resolves the same alias in tests.
- Runtime or script runner resolves the alias in direct execution paths.
Start with a simple example. In tsconfig.json:
\{
"compilerOptions": \{
"baseUrl": ".",
"paths": \{
"@/*": ["src/*"]
\}
\}
\}This says that imports starting with @/ should point to src/. From there, each tool must either read that config directly or be configured with an equivalent mapping.
If you need a broader TypeScript config baseline, see tsconfig.json Best Settings by Project Type.
Checklist by scenario
Use this section as a setup checklist. Pick the scenario that matches your stack and verify each item in order.
1. Base TypeScript setup for any project
Before touching framework-specific settings, make sure the core alias definition is sound.
- Set
baseUrl, usually to.for project-root-relative mappings. - Define
pathsusing a consistent prefix such as@/*. - Make sure the target path matches your actual source layout.
- Prefer one clear alias over many overlapping aliases at first.
Example:
\{
"compilerOptions": \{
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"baseUrl": ".",
"paths": \{
"@/*": ["src/*"],
"@server/*": ["server/*"]
\}
\}
\}Checklist:
- Does
@/*really map tosrc/*? - Are you editing the correct
tsconfigfile in a multi-config project? - Does your app use
src, project root, or a framework-specific directory such asapp?
2. Vite path aliases
Vite projects often compile TypeScript correctly in the editor but still need explicit alias handling in the Vite config unless a plugin is used.
You generally have two approaches:
- Mirror aliases manually in
vite.config.ts. - Use a plugin that reads
tsconfigpaths.
Manual example:
import { defineConfig } from 'vite'
import path from 'node:path'
export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
}
})Checklist for Vite:
- Confirm the alias in
vite.config.tspoints to the same folder astsconfig.json. - Restart the Vite dev server after changing aliases.
- If using Vitest, verify whether your test config inherits Vite resolution as expected.
- If you use a plugin for tsconfig paths, confirm it is loaded in the active config file.
For a broader starter reference, see React + TypeScript Setup Guide for Vite, Next.js, and CRA Alternatives.
3. Next.js TypeScript alias setup
Next.js generally works smoothly with TypeScript aliases when they are defined in the project config, but you still need to verify the exact directories you are targeting.
Typical example:
\{
"compilerOptions": \{
"baseUrl": ".",
"paths": \{
"@/*": ["./src/*"]
\}
\}
\}Checklist for Next.js:
- Use the directory structure your app actually follows:
src/*,app/*, or root-level folders. - Check whether custom tooling around Next.js, such as Jest or storybook-style tooling, also needs alias mapping.
- If imports fail only in tests, the Next.js app config may be fine and the test runner may be the real problem.
If your project mixes React conventions and framework-specific config, keep one canonical alias structure and adapt the tools to it, not the other way around.
4. Node.js TypeScript setup
This is where many developers get caught. TypeScript path aliases help type checking and authoring, but plain Node.js does not automatically understand tsconfig path mappings.
That means this may compile:
import { loadEnv } from '@server/config/env'But direct execution can still fail unless your runtime path resolution is configured.
Checklist for Node:
- Decide whether your code is bundled before execution or run directly by Node.
- If bundled, confirm the bundler rewrites or resolves aliases.
- If run directly, confirm your runtime strategy supports aliases.
- Be extra careful in scripts, CLIs, migration files, and one-off jobs, because they often bypass the main app build flow.
For Node project foundations, see Node.js + TypeScript Setup That Still Works in 2026.
5. Jest with tsconfig paths
Jest commonly needs its own alias map. Even when your app and editor work, Jest may still report module resolution errors unless the alias is translated into moduleNameMapper.
Example pattern:
module.exports = {
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1'
}
}Checklist for Jest:
- Map regex patterns carefully. A small mismatch can break all aliased imports.
- Use
<rootDir>consistently so paths stay stable across environments. - If using multiple aliases, add explicit mappings for each one.
- Clear Jest cache if the config changed but old failures persist.
If your error looks more like a missing type than a path problem, compare it with How to Fix "Cannot find name" and Other Missing Type Errors in TypeScript.
6. ts-node path aliases
ts-node is another place where path aliases may appear to work in the editor but fail in execution. The key question is whether the runtime process is loading support for tsconfig path resolution.
Checklist for ts-node:
- Confirm which command actually runs your script in development.
- Check whether alias support is being registered explicitly.
- Verify the active
tsconfigis the one you expect, especially in monorepos or server/client splits. - Test a minimal aliased import in a small script rather than debugging the whole app at once.
In practice, ts-node, Node, and Jest issues often come down to the same root cause: TypeScript knows the alias, but the runtime environment does not.
7. Monorepo or multi-tsconfig projects
In larger repositories, path alias problems are often caused by configuration drift rather than bad syntax.
Checklist for monorepos:
- Identify the source of truth: root
tsconfig.base.jsonor package-local configs. - Make sure extending configs do not accidentally overwrite
baseUrlorpaths. - Avoid defining slightly different meanings for the same alias in different packages.
- Confirm each tool points at the intended config file.
If you must vary aliases between packages, keep the differences deliberate and documented.
What to double-check
When aliases break, the fix is usually small. The hard part is finding which layer is responsible. Run through these checks before changing multiple files at once.
1. Is the import path valid under the alias?
Check the physical file location and the mapped folder. An import like @/utils/date only works if @/* points to the folder that actually contains utils/date.
2. Are you using the right wildcard pattern?
This is a frequent source of subtle problems:
"@/*": ["src/*"]maps nested imports."@": ["src"]is a different pattern and may not match what you expect.
Use wildcard pairs consistently unless you have a specific reason not to.
3. Are all tools reading the same project root?
In one tool, . may mean the repository root. In another, it may effectively behave relative to a package or config location. If an alias works in one workspace but not another, verify the working directory assumptions.
4. Did you restart the relevant process?
Editors, dev servers, and test runners often cache config. After changing tsconfig.json, restart:
- the TypeScript server in your editor
- the dev server
- the test runner
- any long-running Node process
5. Are you mixing ESM, CJS, and tool-specific resolution rules?
Module system choices can make alias issues look worse than they are. If you are already dealing with import/export compatibility problems, isolate those from path alias debugging. Fix one axis at a time.
6. Does linting understand the same imports?
If ESLint reports unresolved imports while the app runs fine, you may need to align linting resolution with the project config. That is a separate layer from TypeScript itself. See ESLint + TypeScript Flat Config Guide.
7. Is the error really about paths?
Some errors that look like alias issues are actually missing type declarations, wrong file extensions, or package export mismatches. If you are getting TypeScript diagnostic codes, check TypeScript Error Codes List: Meaning, Common Causes, and Fixes.
Common mistakes
These are the mistakes that tend to repeat across frameworks and upgrades.
Defining aliases only in tsconfig.json
This is the most common mistake. tsconfig.json is necessary, but often not sufficient. Build tools, test runners, and runtime execution may need their own resolution settings.
Using too many aliases too early
A small app rarely needs five custom prefixes. Start with one or two:
@/*for main source code- possibly one extra alias for server-only or shared code
The more aliases you add, the more places must stay synchronized.
Pointing aliases at unstable folder layouts
If your directory structure changes often, aliases can become a moving target. Prefer mapping to stable top-level source roots instead of temporary feature folders.
Creating overlapping alias patterns
For example, combining @/*, @components/*, and @shared/* can work, but overlapping patterns make debugging harder. Be intentional about priority and naming.
Ignoring test and script entry points
Main app code may work perfectly while background jobs, migrations, test files, and code generation scripts fail. These paths are often maintained by different tools and deserve their own checks.
Forgetting monorepo inheritance rules
In extended configs, a package-local compilerOptions block may replace or reshape inherited behavior in ways that are easy to miss. Always inspect the effective config, not just the root file.
Treating relative imports as a problem in every case
Aliases are useful, but not every deep import needs them. Relative imports are often clearer inside the same feature directory. Use aliases mainly for cross-cutting imports that would otherwise climb multiple levels.
That balance matters for maintainability. The goal is not zero relative paths. The goal is predictable import resolution.
When to revisit
Path alias setup is worth revisiting whenever the surrounding toolchain changes. Use this short maintenance checklist before a migration, framework upgrade, or project restructuring.
- Before upgrading Vite, Next.js, Jest, or Node tooling
Review whether any config files changed format, location, or defaults. - When moving files between
src, root, or package folders
Verify your existing alias targets still match the real layout. - When adding tests, CLI scripts, or background workers
Check whether those execution paths resolve aliases the same way as the main app. - When splitting a project into packages
Define a clear source of truth for shared TypeScript config and decide whether aliases stay local or become package imports. - When editor behavior and runtime behavior diverge
Assume config drift and audit each tool layer one by one.
A practical review routine looks like this:
- Keep one canonical alias design in
tsconfig. - List every tool that executes or analyzes your code.
- Confirm each tool either reads tsconfig paths directly or has an equivalent mapping.
- Add one small aliased import test in app code and one in tests or scripts.
- Document the setup in your project README so the next upgrade starts from a known baseline.
If you want the shortest version of this article to keep next to your config files, use this final checklist:
- Define
baseUrlandpathscorrectly. - Match your bundler or framework resolver to the same alias.
- Match your test runner to the same alias.
- Match your runtime or script runner to the same alias.
- Restart tools after config changes.
- Revisit the setup after upgrades or folder restructures.
That is the durable lesson behind most TypeScript path alias bugs: import shortcuts are easy to add, but stable resolution requires every layer of the toolchain to agree on what the shortcut means.