Introduction
Migrating a large JavaScript codebase to TypeScript is one of the highest-ROI investments a development team can make. TypeScript catches bugs at compile time, makes refactoring safer, and dramatically improves IDE support. But for large codebases, the migration needs to be done incrementally — you can't rewrite everything at once.
This guide walks through a battle-tested migration strategy that lets you ship TypeScript improvements continuously without blocking feature development.
Step 1: Enable TypeScript Without Breaking Anything
Start by adding TypeScript to your project without changing any existing code. Install the compiler and create a minimal tsconfig:
npm install --save-dev typescript @types/node
npx tsc --initSet these options in your tsconfig.json to start permissively:
{
"compilerOptions": {
"allowJs": true,
"checkJs": false,
"strict": false,
"outDir": "./dist",
"target": "ES2020",
"module": "CommonJS"
},
"include": ["src/**/*"]
}With allowJs: true, TypeScript will compile your existing .js files. With checkJs: false, it won't report errors on them yet. This gives you a working build immediately.
Step 2: Convert Files One at a Time
Rename files from .js to .ts one module at a time, starting with utility functions and leaf modules (files that don't import much). These are the easiest to type because they have few dependencies.
The best order:
- Types and interfaces first (create a types/ directory)
- Pure utility functions
- Data access / API layers
- Business logic
- UI components last (most dependencies)
Use js2ts.com to automatically convert your JavaScript files to TypeScript — paste your JS and get type-annotated TypeScript output instantly.
Step 3: Add Strict Mode Gradually
Once all files are .ts, start tightening the compiler. Enable strict options one at a time to avoid a flood of errors:
"strictNullChecks": true, // add first — catches most bugs
"noImplicitAny": true, // add second
"strictFunctionTypes": true, // add third
"strict": true // finally enable all at onceStep 4: Handle the Most Common Migration Errors
TS2339 — Property does not exist: Add the property to the interface or type, or use a type guard.
TS2345 — Argument type mismatch: Narrow the type at the call site or widen the function parameter type.
TS7006 — Parameter implicitly has 'any' type: Add explicit type annotations to function parameters.
TS2531 — Object is possibly null: Add null checks or use optional chaining (?.) and nullish coalescing (??).
Step 5: Add Runtime Validation with Zod
TypeScript types exist only at compile time. For API inputs and external data, add Zod schemas for runtime validation. Use our TypeScript to Zod converter to generate Zod schemas from your TypeScript interfaces automatically.
Conclusion
A large JavaScript-to-TypeScript migration doesn't have to be a big-bang rewrite. The incremental approach — allowJs first, convert file by file, tighten strict mode gradually — lets you ship improvements without disrupting your team. Start with the utilities, use js2ts.com to speed up the conversion, and your entire codebase can be fully typed in weeks rather than months.
