Overview
Set module and moduleResolution as a pair, set types explicitly, and stop using options that TypeScript 6 deprecated and 7 no longer supports. Use NodeNext for Node ESM, a shared base config split per package, and project references for monorepos. strict and the extra safety flags are in typescript-strict-mode.
Know what TypeScript 6 changed
strictistrueby default;moduledefaults toesnext;targetfloats to the newest ES version (es2025today).typesdefaults to[], so ambient globals no longer load fromnode_modules/@types. Add"types": ["node"](and"vite/client"or similar) orprocessandBufferbecome undeclared.rootDirdefaults to the folder holdingtsconfig.json. Set"rootDir": "./src"if your sources sit deeper and you emit.noUncheckedSideEffectImportsdefaults totrue, andlibReplacementtofalse.
Remove options that 7.0 does not support
Deprecated in 6.0 and unsupported in 7.0: target: es5, downlevelIteration, moduleResolution: node (also called node10), classic, module values amd, umd, systemjs, and none, baseUrl, outFile, esModuleInterop: false, allowSyntheticDefaultImports: false, and alwaysStrict: false. "ignoreDeprecations": "6.0" silences the 6.0 errors but only buys time; 7.0 does not support these options at all.
Replace baseUrl with relative paths entries ("@app/*": ["./src/app/*"]). Interop behavior is always on, so import express from "express" works against CommonJS and the explicit esModuleInterop: true is redundant. If a 6 to 7 upgrade changes diagnostics, --stableTypeOrdering on 6.0 makes its type ordering match 7.0 for diffing; it is a diagnostic aid, not a permanent setting.
Use this baseline for a bundled app
For Vite, Next.js, or Astro the bundler resolves and emits, so tsc only checks.
{
"compilerOptions": {
"target": "ES2023",
"module": "ESNext",
"moduleResolution": "Bundler",
"lib": ["ES2023", "DOM", "DOM.Iterable"],
"types": [],
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitOverride": true,
"verbatimModuleSyntax": true,
"skipLibCheck": true,
"noEmit": true
},
"include": ["src"]
}target is the newest syntax your oldest runtime runs; it never polyfills APIs. skipLibCheck skips .d.ts files, which speeds checking at the cost of missing wrong dependency types. verbatimModuleSyntax requires import type for type-only imports and implies isolatedModules, so do not add both.
Use NodeNext for Node ESM
Node ESM code needs "module": "NodeNext" and "moduleResolution": "NodeNext" together, with explicit file extensions in relative imports and "types": ["node"].
To run .ts through Node’s type stripping, use the options Node’s documentation recommends:
{
"compilerOptions": {
"noEmit": true,
"target": "esnext",
"module": "nodenext",
"rewriteRelativeImportExtensions": true,
"erasableSyntaxOnly": true,
"verbatimModuleSyntax": true
}
}Share a base config; split per package
Put strictness flags in tsconfig.base.json and let each package extends it and choose its own module, outDir, and include. Keep tsconfig.test.json extending the package config when tests need different include or types.
Use project references for monorepos
References make tsc --build compile packages in dependency order and skip unchanged ones. Each referenced package needs composite: true.
{
"extends": "../../tsconfig.base.json",
"compilerOptions": { "composite": true, "rootDir": "src", "outDir": "dist" },
"references": [{ "path": "../shared" }, { "path": "../db" }]
}See monorepo for the wider layout. Case-insensitive filesystems hide wrong import casing, so keep forceConsistentCasingInFileNames: true on to catch it before Linux CI does.