The TypeScript Migration Playbook for Legacy Codebases

7 min readYaseen Khatib · MERN + AI Architect

The instinct when migrating a large JavaScript codebase to TypeScript is to stop the world and rewrite. That instinct kills migrations. The successful playbook is incremental: ship types alongside features, tighten strictness in stages, and never let the codebase be un-runnable for more than a commit. Done this way, a sprawling legacy app becomes type-safe without a single feature freeze.

Allow JS, then advance the front

Turn on allowJs and rename files to .ts one module at a time, starting at the leaves — shared utilities and types — and working inward toward the application core. Each renamed file gets types; everything still importing it keeps working. The migration front advances continuously instead of arriving all at once.

tsconfig.json
// stage 1: coexist; ratchet strictness later, not now
{
  "compilerOptions": {
    "allowJs": true,            // JS and TS live together
    "checkJs": false,           // don't boil the ocean yet
    "strict": false,            // turn on flags one at a time
    "noImplicitAny": true       // the first ratchet that matters
  }
}

Ratchet strictness, never loosen

Strictness is a ratchet: each flag you enable stays enabled. Start with noImplicitAny, fix the fallout, commit. Then strictNullChecks — usually the highest-value flag, because it catches the entire class of null/undefined bugs that plague legacy JS. Add the rest of strict incrementally. Each step is a finite, reviewable diff rather than an unbounded rewrite.

Quarantine the any

You will need escape hatches at the boundaries of untyped third-party code — that is fine, as long as any is contained, named, and tracked, not scattered. A single typed boundary that validates and casts external data keeps the untyped world from leaking into your core. The goal is not zero any on day one; it is no silent any in the parts you control.

A TypeScript migration that requires a feature freeze will be cancelled the first time the business needs to ship. The only migration that finishes is the one nobody has to stop for.

Strict types matter most at the AI boundary — see Type-Safe LLMs.

Looking to architect a similar system?

Let's ship it at AI-speed.

Start a conversation →