This article is so popular that I've bundled its recommendations into a library! It's called @total-typescript/tsconfig, and you can check it out here.
{ "compilerOptions": { /* Base Options: */ "esModuleInterop": true, "skipLibCheck": true, "target": "es2022", "allowJs": true, "resolveJsonModule": true, "moduleDetection": "force", "isolatedModules": true, "verbatimModuleSyntax": true, /* Strictness */ "strict": true, "noUncheckedIndexedAccess": true, "noImplicitOverride": true, /* If transpiling with TypeScript: */ "module": "NodeNext", "outDir": "dist", "sourceMap": true, /* AND if you're building for a library: */ "declaration": true, /* AND if you're building for a library in a monorepo: */ "composite": true, "declarationMap": true, /* If NOT transpiling with TypeScript: */ "module": "preserve", "noEmit": true, /* If your code runs in the DOM: */ "lib": ["es2022", "dom", "dom.iterable"], /* If your code doesn't run in the DOM: */ "lib": ["es2022"] }}
isolatedModules: This option prevents a few TS features which are unsafe when treating modules as isolated files.
verbatimModuleSyntax: This option forces you to use import type and export type, leading to more predictable behavior and fewer unnecessary imports. With module: NodeNext, it also enforces you're using the correct import syntax for ESM or CJS.
strict: Enables all strict type checking options. Indispensable.
noUncheckedIndexedAccess: Prevents you from accessing an array or object without first checking if it's defined. This is a great way to prevent runtime errors, and should really be included in strict.
noImplicitOverride: Makes the override keyword actually useful in classes.
composite: Tells TypeScript to emit .tsbuildinfo files. This tells TypeScript that your project is part of a monorepo, and also helps it to cache builds to run faster.
sourceMap and declarationMap: Tells TypeScript to emit source maps and declaration maps. These are needed so that when consumers of your libraries are debugging, they can jump to the original source code using go-to-definition.
lib: Tells TypeScript what built-in types to include. es2022 is the best option for stability. dom and dom.iterable give you types for window, document etc.
I've been updating this cheatsheet as TypeScript evolves, and as I refine my view of what belongs in a reusable tsconfig.json. Here's the changelog:
2024-04-23: Added verbatimModuleSyntax to the base options. With the introduction of module: Preserve, verbatimModuleSyntax is much more useful. Many applications do 'fake ESM', where they write imports and exports but transpile to CommonJS. Next.js is a common example. Before module: Preserve, verbatimModuleSyntax would error on every single import or export statement because it was expecting a module. With module: Preserve, its scope is narrowed making sure import/export type is used correctly.
2024-04-23: Added noImplicitOverride to the strictness options. Never knew about this option, or the override keyword, until I discovered it while researching my book. noImplicitOverride is a very small improvement at no cost, so why not?