A TypeScript 6.0 Upgrade Checklist: Fix rootDir, types, and baseUrl Before TypeScript 7.0
TypeScript 6.0 is not a minor release where teams can bump a version number and move on. It is better understood as an engineering cleanup release. The official TypeScript 6.0 release notes position it as a transition toward TypeScript 7.0: most familiar TypeScript usage still works, but several older defaults, deprecated options, and ambiguous behaviors are being tightened so projects expose their real configuration boundaries earlier.
That matters for long-lived frontend and Node.js codebases. Many repositories have been compiling successfully for years not because their configuration is clear, but because older TypeScript behavior was doing a lot of implicit work on their behalf. In 6.0, options like strict, rootDir, types, baseUrl, and moduleResolution now force teams to be much more explicit.
If you are migrating from 5.9, the stable approach is not to upgrade first and wait for CI to explode. It is to run through a fixed checklist and sort the change categories deliberately. This is especially important in real codebases with monorepos, path aliases, test-runner globals, Node types, and custom output directories.
First, understand the signal behind 6.0
The direction from the TypeScript team is straightforward. Modern projects mostly target newer JavaScript environments. ESM and bundlers are now the standard path for many new applications. Several defaults and fallback behaviors that used to help compatibility now create ambiguity, performance cost, or misleading success during type-checking.
That is why the key themes in 6.0 are not flashy syntax additions. They are:
- align defaults with modern engineering practice
- reduce implicit behavior and force clearer declarations
- prepare teams for removals coming in TypeScript 7.0
If you need a short transition window, the official guidance allows "ignoreDeprecations": "6.0" in tsconfig.json. That is useful for staging the migration, but it should not become a permanent hiding place. The deprecated options are not expected to survive into 7.0.
Step 1: lock the local baseline before debugging errors
Before changing project code, stabilize the toolchain so TypeScript issues do not get mixed with Node or package-manager drift.
npm install --save-dev typescript@6
npx tsc --version
npx tsc --noEmit --pretty false
If the repository has multiple configs or project references, add these checks as well:
npx tsc -p tsconfig.json --showConfig > tsconfig.expanded.json
npx tsc -b --verbose
This surfaces two practical questions immediately:
- what final config is actually in effect
- which
tsconfigin the reference chain is producing the failure
Many TypeScript upgrade efforts stall because teams are editing the wrong config layer.
Step 2: handle the changed defaults first
TypeScript 6.0 changes several defaults that can impact older codebases quickly.
strict now defaults to true
If your project never declared strict explicitly, the upgrade may suddenly surface new nullability, union, or indexed-access errors. That change is reasonable, but it should not be debugged blindly.
A pragmatic split is:
- older repositories can explicitly set
"strict": falsefor a short-term controlled upgrade - codebases already paying down type debt can keep strict mode and fix the errors directly
The important point is to stop letting the default decide your team policy.
module now defaults to esnext
If your build still assumes CommonJS output, or if downstream scripts read emitted files directly, make the module target explicit instead of relying on legacy expectations.
{
"compilerOptions": {
"module": "nodenext"
}
}
For frontend applications, choose the module mode that matches the bundler and runtime expectations.
target now defaults to the current ECMAScript year
The default target is no longer effectively stuck in the past. That is helpful for teams with clear browser baselines or modern Node deployments, but projects that must emit older JavaScript should lock the target explicitly.
{
"compilerOptions": {
"target": "es2022"
}
}
Step 3: inspect rootDir before your output layout shifts
This is one of the easiest places to get surprised after upgrading. TypeScript 6.0 makes the default rootDir behavior more direct: by default, it is now the directory containing tsconfig.json instead of being inferred from the common source root in the older style.
If dist/index.js suddenly becomes dist/src/index.js, this is usually the first place to look. The standard fix is to declare rootDir explicitly.
{
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist"
},
"include": ["./src"]
}
If your tests or helper scripts live outside the tsconfig directory, revisit rootDir and include together. Do not stop at “the build passes.” Confirm that the emitted directory structure still matches Docker images, deployment scripts, runtime entrypoints, or artifact upload logic.
Step 4: make types explicit immediately
One of the most important changes in 6.0 is that types now defaults to an empty array. This is not a cosmetic adjustment. It is a meaningful change for build predictability and performance.
Older TypeScript behavior effectively enumerated whatever was present under node_modules/@types, which meant many projects got names like process, Buffer, or describe in global scope without ever declaring them. TypeScript 6.0 stops doing that.
The practical fix is simple: declare what you actually need.
{
"compilerOptions": {
"types": ["node", "jest"]
}
}
If you start seeing errors like these:
Cannot find name 'process'Cannot find name 'describe'Cannot find module 'fs' or its corresponding type declarations
check types and the relevant @types/* packages before assuming the application code is broken.
Only during short-term recovery should you consider restoring older behavior with:
{
"compilerOptions": {
"types": ["*"]
}
}
That is a compatibility fallback, not a good long-term configuration strategy. Explicit types entries are more stable and typically better for build performance.
Step 5: replace baseUrl with explicit path mappings
If your repository has long relied on config like this:
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"@app/*": ["app/*"],
"@lib/*": ["lib/*"]
}
}
}
then the TypeScript 6.0 migration is the right time to rewrite it. The official guidance deprecates baseUrl and recommends that most projects remove it and move the prefix directly into paths.
A clearer version looks like this:
{
"compilerOptions": {
"paths": {
"@app/*": ["./src/app/*"],
"@lib/*": ["./src/lib/*"]
}
}
}
This is better for several reasons:
- TypeScript resolution becomes closer to actual bundler runtime behavior.
baseUrlno longer acts like a vague lookup root that can make invalid imports look acceptable.- alias intent becomes much easier to understand in monorepos and multi-package repos.
If you truly used baseUrl as a real global lookup root, the official notes describe a catch-all * mapping as a replacement. In practice, that is uncommon. Most teams should use the upgrade to make path mappings explicit instead.
Step 6: remove outdated module-resolution and output patterns
TypeScript 6.0 is direct about a few old configuration modes: they are no longer where modern projects should stay.
moduleResolution: classic is gone
If your repository still carries this setting, migrate now. Frontend apps will usually prefer bundler, while Node-oriented projects will often prefer nodenext.
{
"compilerOptions": {
"moduleResolution": "bundler"
}
}
or:
{
"compilerOptions": {
"moduleResolution": "nodenext"
}
}
outFile has been removed
If a project still depends on outFile, it is asking TypeScript to behave like a bundler. TypeScript 6.0 closes that path. Move those builds to tools like Vite, esbuild, Rollup, or Webpack instead.
esModuleInterop: false and allowSyntheticDefaultImports: false are no longer the safe default path
Older imports that depended on stricter CommonJS interop behavior may need review. When necessary, update those imports so runtime expectations and type-checking behavior stay aligned.
Step 7: turn post-upgrade verification into a fixed routine
Updating tsconfig is not enough. The validation work should be repeatable.
npx tsc --noEmit
npm test
npm run build
For monorepos, add:
npx tsc -b --clean
npx tsc -b
Then review four categories deliberately:
- whether failures are concentrated in missing type declarations
- whether emitted output directories have shifted
- whether path aliases behave consistently in tests, dev, and production builds
- whether CI and local machines disagree
If those checks are still unclear, the migration is not actually finished even if one build turns green.
Common mistakes
Adding ignoreDeprecations and never returning to it
That is a transition aid, not a destination. If the team leaves the debt in place, TypeScript 7.0 will remove the safety net at a worse time.
Fixing compiler errors without reviewing emitted artifacts
rootDir, module, and paths changes do not always fail loudly. Sometimes they only change output layout or runtime resolution behavior. A passing type-check is not the whole story.
Treating types: ["*"] as the final solution
That restores older behavior quickly, but it is not the cleanest long-term configuration. Explicit type lists remain the more reliable engineering choice.
Final takeaway
The real value of a TypeScript 6.0 migration is not only staying current. It is using the upgrade to make configuration boundaries explicit. rootDir defines output structure, types defines global type visibility, baseUrl deprecation forces clearer alias mapping, and the removal of classic and outFile is a reminder to stop leaning on patterns that no longer fit modern toolchains.
If your team expects to keep a codebase healthy through 2026 and beyond, it is worth treating TypeScript 6.0 as a configuration review, not just a version bump. Once the implicit behavior is replaced with explicit settings, the eventual move toward TypeScript 7.0 becomes much lower risk.