"strict": true in tsconfig is the floor, not the ceiling. The five extra flags below catch real bugs that pure-strict misses. This post is the working setup.
The floor
{
"compilerOptions": {
"strict": true,
"target": "es2024",
"module": "esnext",
"moduleResolution": "bundler",
"esModuleInterop": true,
"skipLibCheck": true,
"isolatedModules": true
}
}
Standard. What ships in tsc --init mostly.
Beyond strict
noUncheckedIndexedAccess
const xs: number[] = [1, 2, 3];
const x = xs[10]; // Pre-flag: x: number. Post-flag: x: number | undefined
Forces handling of “this index might not exist.” Catches bugs everywhere arrays / records get accessed without bounds check.
Migration pain: real. Reward: real.
exactOptionalPropertyTypes
type User = { name: string; nickname?: string };
const u: User = { name: "A", nickname: undefined }; // ⛔ with flag
Optional means “may be absent,” not “may be undefined.” Distinguishes { x?: T } from { x: T | undefined }. Catches API mismatches.
noPropertyAccessFromIndexSignature
type Headers = { [key: string]: string };
const h: Headers = {};
h.contentType; // ⛔ with flag — must use h["Content-Type"]
Forces explicit indexing for index-signature types. Catches typos and assumptions.
noImplicitOverride
class Base {
greet() { return "hi"; }
}
class Sub extends Base {
greet() { return "hey"; } // ⛔ must mark override
}
Without override, you might shadow the base method by accident. Annotation makes intent explicit.
verbatimModuleSyntax
import { Foo } from "./mod"; // ⛔ if Foo is type-only
import type { Foo } from "./mod"; // ✅
Forces you to mark type-only imports. Helps with bundlers (they can drop type-only imports cleanly).
Other useful flags
{
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"forceConsistentCasingInFileNames": true,
"noImplicitReturns": true,
"allowUnreachableCode": false
}
Each catches a specific class of bug or stylistic accident.
Module resolution in 2026
"module": "preserve", // for libraries
"module": "esnext", // for apps bundled with Vite/esbuild/Bun
"moduleResolution": "bundler", // matches modern bundlers
"bundler" resolution matches what Vite, esbuild, Bun do. Avoids the tsc --moduleResolution node legacy behaviors.
For libraries: rougher checks
{
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"stripInternal": true
}
Plus ban any returns from public API:
"noExplicitAny": "error" // via ESLint / Biome
Migration strategy
For existing code:
- Enable
strictfirst if not already. Fix. - Add one extra flag at a time. Fix.
- Repeat.
Don’t try to enable everything at once on a 100k LoC codebase. The ergonomic tax is real; ramp.
What I’d ship today
For a new TypeScript project:
{
"compilerOptions": {
"target": "es2024",
"module": "esnext",
"moduleResolution": "bundler",
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"verbatimModuleSyntax": true,
"skipLibCheck": true,
"isolatedModules": true,
"noUnusedLocals": true,
"noUnusedParameters": true
}
}
Strict ceiling. Catches the most bugs. Combined with Biome / ESLint , you have rigorous TS.
Read this next
- Biome vs ESLint + Prettier in 2026
- Bun vs Node.js in 2026
- Modern TypeScript Backend with Hono on Bun
- Drizzle ORM Deep Dive
If you want my tsconfig.json template per project shape, it’s at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .