The tsconfig.json file is the control center of every TypeScript project. It tells the compiler which files to include, how strictly to check them, what JavaScript to emit, and how to resolve modules. Understanding a dozen key options takes you from copy-pasting configs to confidently tuning your own.
Generate a starter config with npx tsc --init. A practical modern configuration looks like this:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"outDir": "./dist",
"rootDir": "./src",
"sourceMap": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}include and exclude control which files are part of the project; everything else lives under compilerOptions.
ES2022 is safe for current browsers and Node.js; older targets transpile modern syntax down.NodeNext for Node.js projects (it respects package.json "type"), or ESNext when a bundler like Vite or esbuild handles modules.src/ tree to a dist/ tree, keeping compiled output out of your source folders..map files so debuggers and stack traces point at your TypeScript, not the generated JavaScript."strict": true enables a bundle of checks, and every serious project should turn it on from day one:
null and undefined must be handled explicitly. The single most valuable flag.any..call/.apply.Retrofitting strictness onto a large lax codebase is painful; starting strict is nearly free. Worth adding on top of strict:
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"noFallthroughCasesInSwitch": truenoUncheckedIndexedAccess makes array[i] return T | undefined, forcing bounds thinking — strict but bug-catching.
.d.ts files in node_modules, dramatically speeding up builds and avoiding errors in third-party types you cannot fix.import config from "./config.json" with types.["ES2022", "DOM"] for browser code — drop "DOM" for pure Node projects.Tired of ../../../utils? Map clean prefixes to folders:
"baseUrl": ".",
"paths": { "@/*": ["src/*"] }Then import { log } from "@/utils/log". Note that paths only affects type checking — your bundler or runtime needs a matching alias configuration.
tsc is used with "noEmit": true purely as a type checker in CI."declaration": true to emit .d.ts files so consumers get your types.tsconfig.base.json is shared via "extends", and project references ("composite": true) enable fast incremental builds across packages.tsconfig.json defines project files, checking strictness, and emit format in one place.strict; consider noUncheckedIndexedAccess for maximum safety.target controls emitted syntax; module/moduleResolution should be NodeNext for Node or ESNext + bundler.esModuleInterop and skipLibCheck are near-universal quality-of-life settings.extends to share configs and noEmit when a bundler owns compilation.Next lesson: Using TypeScript with React and Node.js — applying everything in real frameworks.