A tsconfig.json decides how strictly TypeScript checks your code and what
JavaScript it emits. The same source can be safe or unsafe depending on a handful
of flags. This reference covers the compilerOptions you tune most, with each
flag’s type, default, and concrete effect.
How it works
Compiler options fall into a few jobs. Type-checking flags (the strict
family plus extras like noUncheckedIndexedAccess) change which programs are
accepted. Module flags (module, moduleResolution, target) change how
imports resolve and what runtime features survive. Emit flags
(declaration, sourceMap, outDir, noEmit) change what files are written.
Setting strict: true is shorthand for enabling its whole family. The reference
marks which flags belong to that family so you can see exactly what strict mode
implies and decide whether to layer on the non-strict extras.
Tips
For a browser app bundled by Vite or esbuild, a sensible base is
target: "ESNext", module: "ESNext", moduleResolution: "bundler",
strict: true, and noEmit: true (the bundler does the emit). Add
noUncheckedIndexedAccess and exactOptionalPropertyTypes once the codebase is
clean for an extra safety margin. For a published library, prefer NodeNext
resolution, emit declarations, and keep isolatedModules: true so any
single-file transpiler can build it.
Recommended starting configs for common project types
Next.js app (App Router):
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noEmit": true,
"jsx": "preserve",
"incremental": true,
"plugins": [{ "name": "next" }]
}
}
Node.js CLI or backend service (ESM):
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"noUncheckedIndexedAccess": true,
"outDir": "dist",
"declaration": true,
"sourceMap": true
}
}
Published npm library:
{
"compilerOptions": {
"target": "ES2020",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"isolatedModules": true,
"declaration": true,
"declarationMap": true,
"outDir": "dist"
}
}
The strict family — exactly what it enables
strict: true is a grouped shorthand. It is equivalent to setting all of these individually:
| Flag | What it catches |
|---|---|
noImplicitAny | Variables inferred as any are an error |
strictNullChecks | null and undefined are not assignable to other types |
strictFunctionTypes | Function parameter types are checked contravariantly |
strictBindCallApply | .bind, .call, .apply are type-checked |
strictPropertyInitialization | Class properties must be assigned in the constructor |
noImplicitThis | this with an implicit any type is an error |
useUnknownInCatchVariables | Caught errors are unknown not any (TypeScript 4.4+) |
alwaysStrict | Emits "use strict" in every JS file |
You can disable any member after enabling strict — for example "strictPropertyInitialization": false if you use a DI framework that assigns properties externally.
Flags worth adding beyond strict
noUncheckedIndexedAccess— makesarr[i]returnT | undefined. Prevents off-by-one access bugs at the type level.exactOptionalPropertyTypes— distinguishes{ a?: string }(key may be absent) from{ a: string | undefined }(key present, value may be undefined).noImplicitOverride— requires theoverridekeyword on methods that override a parent class method, preventing accidental overrides when the base class changes.noPropertyAccessFromIndexSignature— forces bracket notation for index-signature access, making it visible that the lookup might be undefined.