Turning on TypeScript strict mode in an existing JavaScript codebase feels like opening a can of worms — because it is one. The good news is that the worms are bugs you already had, just invisible until now. Here’s a migration path that doesn’t require stopping feature work for a month.
Step 1: add TypeScript without strict mode
Install the compiler and let JS and TS files coexist first. Don’t touch strictness yet.
npm install --save-dev typescript @types/node
npx tsc --init
Rename one or two low-risk files from .js to .ts to confirm your build pipeline (bundler, test runner, linter) handles TypeScript output correctly before going further.
Step 2: a tsconfig.json that actually works
Start with allowJs enabled and strict flags off, so the whole project compiles from day one.
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM"],
"module": "ESNext",
"moduleResolution": "bundler",
"allowJs": true,
"checkJs": false,
"outDir": "dist",
"rootDir": "src",
"strict": false,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"noEmitOnError": false
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
Step 3: turn on strict flags one at a time
Never flip "strict": true in one commit on a large codebase — you’ll get thousands of errors at once and nobody will fix them. Enable flags incrementally instead, in this order:
noImplicitAny— forces you to type function parameters and variables. Usually the biggest error count, but the most mechanical to fix.strictNullChecks— the flag that actually catches bugs (null/undefined access). Expect this to take the longest.strictFunctionTypesandstrictBindCallApply— smaller surface area, quick wins.strictPropertyInitialization— mostly affects classes; fix constructors or mark fields optional.- Finally set
"strict": true, which bundles all of the above plusalwaysStrictanduseUnknownInCatchVariables.
Step 4: convert files bottom-up
Convert leaf modules (utilities, helpers with no internal dependencies) before the files that import them. This way each conversion immediately benefits from types instead of fighting any everywhere.
- Use
// @ts-expect-erroras a temporary escape hatch, and grep for it weekly to track remaining debt. - Set
checkJs: truepartway through to get type-checking on remaining.jsfiles without converting them yet. - Add a CI check that fails if the count of
anyor@ts-expect-errorcomments increases from the previous build.
What nobody tells you before you start
- Third-party libraries without types will generate the noisiest errors — check
@types/packages exist before blaming your own code. - Strict mode migrations surface real null-reference bugs; treat every new error as a potential production bug, not just noise to silence.
- Budget roughly one week per 10,000 lines of code for a careful, incremental migration on an active codebase.
Useful next reads
If your team is still ramping up on JavaScript fundamentals before tackling TypeScript, see how to learn JavaScript in 2026 without getting overwhelmed.
Quick FAQ
Should I migrate the whole codebase at once?
No. Convert file by file, starting with utilities and leaf modules, and keep the build green throughout.
How long does a strict mode migration usually take?
For an active mid-size codebase, expect weeks, not days — most of the time goes into strictNullChecks.
Can I mix strict and non-strict files during migration?
Yes, using per-file // @ts-nocheck or a gradual strict rollout via tsconfig overrides for specific directories.
Leave a Reply