Quick answer: tsconfig.json tells the compiler which files to include and how strict to be. For a new project, start from tsc --init and keep strict on. The options that matter most are target, module, moduleResolution, strict, verbatimModuleSyntax and skipLibCheck. Current TypeScript deprecates target: es5 and moduleResolution: node10.
Table of Contents
A modern starting point
{
"compilerOptions": {
"target": "es2022",
"module": "preserve",
"moduleResolution": "bundler",
"strict": true,
"verbatimModuleSyntax": true,
"skipLibCheck": true,
"noUncheckedIndexedAccess": true,
"noEmit": true
},
"include": ["src"]
}
| Option | What it does |
|---|---|
target | The JavaScript version emitted — es2022 or later today |
module | The module syntax; preserve for bundlers, nodenext for Node |
moduleResolution | How imports are resolved; bundler for bundled apps |
strict | Turns on the whole family of strict checks |
verbatimModuleSyntax | Honours import type exactly as written |
noEmit | Type-check only; let the bundler emit JavaScript |
Choosing module settings by project type
There is no single correct pair. The right answer depends on how the code runs.
// bundled browser app (Vite, Angular, Next.js)
{ "module": "preserve", "moduleResolution": "bundler" }
// Node.js app or library
{ "module": "nodenext", "moduleResolution": "nodenext" }
bundler lets you write extensionless imports the way a bundler expects. nodenext models Node’s real rules, including that ESM imports need a file extension. Picking the wrong one produces a flood of “cannot find module” errors that look like a broken install but are really a configuration mismatch.
What current TypeScript removed or deprecated
target: "es5"anddownlevelIteration— deprecated, and unsupported in TypeScript 7. Target ES2022 or later.moduleResolution: "node"(node10) — deprecated in favour ofbundlerornodenext.baseUrl— deprecated; writepathsrelative to the config file instead.module: "amd" | "umd" | "systemjs" | "none"— no longer supported. Use a bundler for bundling.import x from "./f" assert { type: "json" }— theassertskeyword is replaced bywith.
If your config contains any of these, expect errors after upgrading. They are migration items, not bugs.
strict is not one option, it is a family
strict: true switches on several checks at once, most importantly strictNullChecks, which is what makes undefined a type you must handle. Two additions beyond the preset are worth turning on:
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true, // arr[0] is T | undefined
"noImplicitOverride": true, // require the override keyword
"exactOptionalPropertyTypes": true // distinguish missing from undefined
}
}
noUncheckedIndexedAccess is the strictest of the three and the one people turn off first, because it forces a check before using any indexed access. On a new project, leave it on: it prevents the most common runtime crash in JavaScript, reading a property of undefined.
Project references for large codebases
For a monorepo, split the config into a base file and per-package files that extend it, then use references so the compiler builds packages in dependency order and caches results.
// tsconfig.json
{ "extends": "./tsconfig.base.json",
"references": [{ "path": "./packages/ui" }] }
// packages/ui/tsconfig.json
{ "extends": "../../tsconfig.base.json",
"compilerOptions": { "composite": true, "outDir": "dist" } }
That completes the TypeScript track. To learn TypeScript and Angular properly with a mentor, Ampersand Academy teaches web development one-to-one.
Frequently asked questions
What is the most important tsconfig option?
strict, because it enables the whole family of strict checks, including treating null and undefined as types you must handle. Everything else is tuning around that.
Should I use moduleResolution bundler or node?
Use bundler for an app built by a bundler such as Vite or Angular, and nodenext for a Node.js project. The old node setting is deprecated.
What is verbatimModuleSyntax?
It makes the compiler honour your import and export statements exactly, including import type, so type-only imports are erased reliably and never leak into the bundle.
Why does my tsconfig throw errors after upgrading TypeScript?
Recent versions deprecated options such as target es5, downlevelIteration, moduleResolution node and baseUrl. Update the config to the current equivalents, such as es2022 and bundler.
What is noUncheckedIndexedAccess?
It makes indexed access return a possibly undefined type, so arr[0] must be checked before use. It prevents the most common runtime crash, reading a property of undefined.

