How to Migrate From baseUrl in TypeScript 7 Using Paths
TypeScript 7 deprecates using baseUrl solely for module resolution (non-relative imports like import utils from "utils/helpers"). The replacement is paths with explicit mappings in your tsconfig.json. If you were using baseUrl only to enable shorthand imports from your src directory, you can drop it entirely and replace it with a paths entry that points to the same root.
Before: The Deprecated baseUrl Pattern
This tsconfig.json pattern triggers the deprecation warning. Setting baseUrl to "./src" lets you write bare imports like import { db } from "lib/database", which resolves to src/lib/database.ts.
// tsconfig.json (DEPRECATED pattern — triggers warning in TS 7).
{
"compilerOptions": {
"baseUrl": "./src",
"outDir": "./dist",
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16"
},
"include": ["src"]
}
After: Migrate to Explicit Paths Mappings
Remove baseUrl and add a paths mapping that recreates the same resolution behavior. The wildcard * pattern captures everything you previously resolved through baseUrl. Note that paths no longer requires baseUrl to be set. This changed in TypeScript 5.0, and it's the foundation of the migration.
// tsconfig.json (migrated — no baseUrl).
{
"compilerOptions": {
"paths": {
"*": ["./src/*"]
},
"outDir": "./dist",
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16"
},
"include": ["src"]
}
Better Approach: Use Scoped Path Aliases Instead of a Catch-All
The catch-all "*" pattern works but is fragile. It can shadow node_modules packages if you accidentally name a folder the same as an npm package. The TypeScript team recommends explicit, prefixed aliases instead. Prefer this approach for any new project or if you have the bandwidth to update imports.
// tsconfig.json (recommended — scoped aliases).
{
"compilerOptions": {
"paths": {
"@lib/*": ["./src/lib/*"],
"@components/*": ["./src/components/*"],
"@services/*": ["./src/services/*"]
},
"outDir": "./dist",
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16"
},
"include": ["src"]
}
Update Your Imports to Match the New Aliases
With scoped aliases, your import statements change from bare paths to prefixed paths. This is a mechanical find-and-replace across your codebase.
// Before (relied on baseUrl).
import { db } from "lib/database";
import { Button } from "components/Button";
// After (uses scoped path aliases).
import { db } from "@lib/database";
import { Button } from "@components/Button";
Gotcha: Runtime Bundlers Need Matching Path Config
TypeScript paths only affect type checking and compilation. They don't rewrite imports in emitted JavaScript. Your bundler or runtime must understand the same aliases.
For Vite (or any Rollup-based tool), use vite-tsconfig-paths or configure resolve.alias directly.
// vite.config.ts — sync path aliases with TypeScript.
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({
plugins: [tsconfigPaths()],
});
For Jest, add a moduleNameMapper in your Jest config that mirrors the paths entries.
// jest.config.ts — mirror tsconfig paths.
export default {
preset: "ts-jest",
moduleNameMapper: {
"^@lib/(.*)$": "/src/lib/$1",
"^@components/(.*)$": "/src/components/$1",
"^@services/(.*)$": "/src/services/$1",
},
};
Gotcha: baseUrl Is Only Deprecated for Module Resolution
If you're already using baseUrl in combination with paths, the migration is simpler. Remove the baseUrl line and make your paths values relative to the tsconfig.json location instead of relative to baseUrl. For example, if baseUrl was "./src" and you had "@lib/*": ["lib/*"], change that to "@lib/*": ["./src/lib/*"].
Note that baseUrl still affects how tsc resolves outDir and rootDir when they're relative. This behavior isn't what's being deprecated. The deprecation targets the implicit "treat every non-relative import as relative to baseUrl" resolution, which caused confusion and silent misresolution for years.
If you use Node.js subpath imports (the #imports field in package.json), that's another viable alternative that works at runtime without any bundler plugin. TypeScript 5.4+ natively supports it with moduleResolution: "Node16" or "Bundler". For pure Node.js projects without a bundler, subpath imports are arguably the cleanest long-term solution because the alias lives in package.json and Node itself understands it.