You cannot stop shipping for six months to "do the TypeScript migration." Here is the incremental playbook that actually finishes.
JavaScript codebases that survive long enough develop a critical mass of bugs that types would have caught. By the time the team agrees TypeScript is worth migrating to, the codebase is too big to migrate in a single push. The trick is to migrate incrementally without slowing feature delivery.
Phase 1: turn on the compiler, leave everything as JavaScript
Add tsconfig.json with allowJs: true and checkJs: false. Configure the build to emit through the TypeScript compiler. The codebase still runs unchanged.
Phase 2: type the boundaries
Pick the highest-leverage boundaries first: API request and response types, database row types, environment variables, public function signatures of shared utilities. Convert these files first. Even with the rest of the code still JavaScript, the type information leaks helpfully into the IDE.
Phase 3: enable noImplicitAny per directory
Use the files array in tsconfig.json to enforce strict typing only on directories that have been migrated. New code goes into the strict zone. Old code remains untyped JS until it is touched.
Phase 4: ratchet up strictness
One flag at a time, in this order: noImplicitAny, strictNullChecks, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, noImplicitThis, useUnknownInCatchVariables. Each flag fixes a real class of bugs.
The metric that matters
Track the percentage of files that are .ts versus .js. Publish the chart on the engineering dashboard. Make it visible in the README. The line will climb without anyone needing to "do a migration sprint."
What we skipped
We do not waste effort migrating low-traffic files (admin scripts, one-off migrations, dead code). We do not type test files until the code under test is typed. We do not block feature work to clear type errors in pre-existing JavaScript — fix what you touch, leave what you don't.