TypeScript refactor for AI apps — remove any types, ship a type-safe refactor of AI-generated code from Lovable, Bolt, v0 and Cursor
TypeScript refactor for AI apps turns AI-generated code refactor sprawl into a locked, type-safe refactor. Lovable, Bolt, v0 and Cursor ship TypeScript the way you'd ship JavaScript — 'any' everywhere, no strict mode, type mismatches between preview and prod. We remove any types, enable strict, add Zod at IO boundaries, and lock the prop contracts so the AI can't silently break working features on the next prompt.
Why AI builders ship broken TypeScript refactor
TypeScript's value is contracts: this function accepts this shape, returns that shape, and the compiler stops you if you lie. AI builders treat TypeScript as JavaScript with decoration. They annotate with 'any' when the shape is unclear, they skip strict mode because it makes the model's output fail to compile, they cast liberally to silence the compiler instead of fixing the underlying bug. The result is code that type-checks but lies — the preview works, the deploy fails, and nobody can tell why.
This compounds the regression loop. Without real types, the AI has no signal when its latest prompt broke a downstream caller. It changes a function signature, the caller still compiles because everything's 'any', and you discover the break at runtime. Strict TypeScript plus real interfaces is the fastest way to make Lovable, Bolt and Cursor stop breaking working features — because the compiler now catches what the model can't.
Which AI builder shipped your broken TypeScript refactor?
The TypeScript refactor failure mode usually depends on the tool that shipped the code. Find your builder below, then read the matching problem page.
| AI builder | What breaks in TypeScript refactor | Go to |
|---|---|---|
| Lovable | tsconfig.strict disabled, 'any' on every API response, no Zod validation | Lovable rescue → |
| Bolt.new | Type casts to silence errors; preview and prod return different shapes | Bolt rescue → |
| v0 | Component props typed as 'any' or untyped; no shared types package | v0 rescue → |
| Cursor | By file seven signatures drift; callers not updated | Cursor regression loop → |
| Replit Agent | Mixed JS/TS files, no root tsconfig, implicit any everywhere | Replit rescue → |
| Claude Code | Good at writing types when asked; rarely enables strict by default | Claude Code rescue → |
| Windsurf | Large codebase, stale types, type-only imports in wrong places | Windsurf rescue → |
| Base44 | Limited TS surface; we migrate to Next.js + proper types | Base44 escape → |
Anatomy of a TypeScript regression loop in an AI-built app
A fintech founder on Lovable described the loop exactly the way Medium's Nadia Okafor did in Vibe Coding in 2026: 'the filter worked, but the table stopped loading. I asked it to fix the table, and the filter disappeared.' Every week, the AI broke a working feature. Every week, the founder spent a day and dozens of credits getting it working again. We opened the repo and the reason was unambiguous: strict mode was off, tsconfig was barely configured, and 340 instances of `: any` sat in the payment flow alone. Every function signature was a suggestion.
The concrete cascade looked like this. Cursor was asked to add a new status ('refunded') to the order type. It added the literal to one interface in one file. It didn't update the discriminated union in the second file. It didn't update the switch statement in the third file, because the switch's default branch was `(status: any) => ...`, which happily swallowed the new value. The payment confirmation email went out for refunded orders because the condition `if (status !== 'cancelled')` was true. Three customers got refunded and also charged a reminder invoice. No compiler error at any stage. The regression wasn't found by tests because there were no tests on that path.
“We flipped strict mode on at the tsconfig level, then fixed the resulting 1,200 errors domain by domain — API layer first, then forms, then components.”
The refactor took three weeks. We flipped strict mode on at the tsconfig level, then fixed the resulting 1,200 errors domain by domain — API layer first, then forms, then components. Every PR stayed green in CI. We introduced Zod at every IO boundary (Stripe webhooks, Supabase reads, form inputs, env vars) and derived types from the schemas so the types could never drift from the runtime checks. We shipped a .cursorrules file that tells the AI to respect strict mode. Six weeks later the founder logged zero 'the AI broke it' incidents. That was the number before refactor: four per week.
The durability of the fix matters more than the one-time cleanup. Strict TypeScript gives the AI builder a feedback loop it previously lacked. Before: Cursor renames a field, the caller still compiles because everything's `any`, the break surfaces at runtime days later. After: Cursor renames a field, the compiler lights up immediately, the AI sees the error in its own terminal and fixes the caller in the same session. The regression loop doesn't stop because the founder got better at prompting; it stops because the compiler is now talking back. Several clients have reported that their AI builder simply started producing better code after the refactor — same tool, same model, new contracts. Strict types are, in effect, the cheapest prompt engineering there is.
This is also the fastest wedge against the specific pain pattern Medium's Vibe Coding in 2026 documented: 'the filter worked, but the table stopped loading.' Without strict types, there's no mechanism to catch that a shared type was used inconsistently across components. With strict types plus Zod at IO boundaries, a shape change in one place forces explicit handling everywhere the shape flows. The AI can't silently break working features because the compiler won't let it silently do anything.
What a TypeScript refactor rescue engagement ships
From first diagnostic to production handoff — the explicit steps on every TypeScript refactor engagement.
- 01
Free diagnostic
We scan your repo for any counts, tsconfig settings, missing return types, and unsafe casts. You get a written 'type health' report in 48 hours.
- 02
Fixed-price refactor scope
We quote the refactor as a fixed project: enable strict, kill anys by domain (API, components, forms), add Zod at IO boundaries, ship tests.
- 03
Strict mode, module by module
We don't flip strict and leave 1,000 errors. We enable strict, fix per directory, and ship in small reviewable PRs. Green CI at every step.
- 04
Zod at the boundaries
API responses, form inputs, env vars, query params — anywhere untrusted data enters the app, we validate with Zod or Valibot and derive the types from the schema.
- 05
Handoff with rules
We ship a .cursorrules, CLAUDE.md, and tsconfig that stops the AI from silently reintroducing anys. Regressions get caught at commit time.
Every TypeScript refactor rescue audit checks
The diagnostic pass on every TypeScript refactor rescue. Each item takes under 10 minutes; together they cover the patterns that cause 90% of AI-built-app failures.
- 01tsconfig.strict
Should be true. If false, the compiler isn't enforcing nulls, implicit anys, or function parameter variance. Almost every AI-built repo we audit ships with this off.
- 02Instances of ': any' and 'as any'
Counted across the repo. Over 50 is a high finding. We map each to its domain (API, components, forms) to plan the refactor.
- 03Declared return types on exported functions
Inferred return types silently drift. Declared types force the compiler to verify the implementation matches the contract.
- 04Zod or Valibot at IO boundaries
API responses, form submissions, webhook payloads, env vars. If untrusted data reaches the app without validation, we flag it.
- 05@ts-ignore and @ts-expect-error without comments
We treat every silenced error as a bug to triage. Legitimate suppressions get an inline comment; drive-by suppressions get fixed.
- 06Shared types package or types/ directory
API and UI should share types. If the UI duplicates the API's shapes, they will drift. We consolidate.
- 07No-any, no-unsafe-* ESLint rules
The typescript-eslint recommended-strict rule set, enforced in CI. Stops reintroduction of anys on future PRs.
- 08Discriminated unions for states
Status types should be `'idle' | 'loading' | 'success' | 'error'` with narrowing. Boolean flags for complex state are a regression vector.
- 09Readonly props and readonly arrays where appropriate
Catches accidental mutation, especially in React components.
- 10.cursorrules and CLAUDE.md for AI discipline
These files tell the AI tool to respect strict types, avoid anys, and run tsc before committing. They materially reduce regressions.
Common TypeScript refactor patterns we fix
These are the shapes AI-generated code arrives in — and the shape we leave behind.
01The User type is defined in the API layer as `{ id: string; email: string; role: string }` and in the UI layer as `{ id: number; email: string; isAdmin: boolean }`. They drift every sprint. Deploys ship with runtime mismatches nobody noticed.01Single source of truth in a shared types package or API definition. UI and server both import from the same module. Any change forces a visible update on every consumer.01`const res = await fetch('/api/orders'); const data: any = await res.json(); return data.orders.map(o => o.total);`01Zod schema for OrderResponse, parsed at the boundary, type inferred from schema, consumers receive fully typed data.01OrderStatus literal union defined in three places, all different. Adding a new status updates one; the other two accept 'any string' via `as any` casts.01Single OrderStatus defined once, re-exported. Exhaustive switch ensures every case is handled. Adding a status breaks the build until all sites are updated.01`const user = auth.currentUser as User;` — user is actually `User | null`, app crashes on the first unauthenticated render path.01Explicit null check, narrowed type, exhaustive handling of the null case.01Form state typed as `Record<string, any>`, submitted to an API route that accepts `any`, inserted into Postgres with no shape check.01Zod schema for the form, React Hook Form + zodResolver, server route parses with the same schema, Postgres insert fully typed.01`user!.name.toUpperCase()` everywhere. Any one null crashes the component.01Proper optional chaining, default rendering when data is incomplete, narrowing guards.01`onClick={(e: any) => handleClick(e.target.value)}` — event target types lost, wrong element assumptions, runtime bugs when a label fires the handler.01Proper MouseEvent<HTMLButtonElement>, typed handler signature, compiler catches the mismatch.01`process.env.SUPABASE_URL` typed as `string | undefined`, used directly with `!`. App ships fine but crashes on first deploy where the var is missing.01Typed env module parsing process.env with Zod at import. App refuses to boot with a clear error if any required var is missing.TypeScript refactor red flags in AI-built code
If any of these are true in your repo, the rescue is probably worth more than the rewrite.
Fixed-price TypeScript refactor engagements
No hourly meter. Scope agreed up front, written fix plan, delivered on date.
- turnaround
- Free rescue diagnostic
- scope
- 30-min call + type-health report in 48 hours.
- turnaround
- AI-generated code cleanup
- scope
- Strict mode, interfaces, Zod, lint rules.
- turnaround
- Break the fix loop
- scope
- Two weeks — types + tests + architecture pass.
What TypeScript refactor rescues actually cost
Anonymized, representative scopes from recent TypeScript refactor rescues. Every price is the one we actually quoted.
A solo founder on Bolt with 80 any-usages in a 3,000-line codebase. Shipping regressions weekly. Wants strict mode and Zod at the boundaries.
- Scope
- Enable strict mode, kill anys in two or three domains, add Zod at API routes, ship .cursorrules.
- Duration
- 1 week
A seed-stage team on Cursor with a 15k-line Next.js app. 340 anys, 12 @ts-ignores, no tests. The AI regresses something every deploy.
- Scope
- Full strict-mode migration, Zod at every boundary, discriminated unions for state, 60 PRs with green CI at each step.
- Duration
- 3 weeks
A growth-stage SaaS with 40k lines, mixed JS/TS files, shared types duplicated across backend and frontend, JIT Stripe webhook handlers with no validation.
- Scope
- Migrate remaining JS to TS, unify shared types package, introduce tRPC or typed REST, full Zod layer, ship linting in CI.
- Duration
- 5-6 weeks
Strict mode in place, type the boundaries, or full tRPC migration?
The first option — strict mode plus killing anys in place — is the most common rescue and works for the majority of AI-built apps. The codebase stays structurally the same; we flip the compiler flags, fix the surfaced errors domain by domain, and ship .cursorrules / CLAUDE.md to keep the AI from regressing. This is the right call when the existing API surface is roughly stable and the bug pattern is regression rather than architecture.
The second option — strict mode plus Zod at every boundary — is what we recommend when the app accepts external data in many places (form submissions, webhook payloads, third-party API responses, user-generated content). Zod at the boundaries gives you parsed, typed values flowing through the app, and the schemas can be composed and reused so a single change propagates correctly. This adds about a week to the engagement compared with strict-only and is almost always worth it for any production app.
The third option — a full tRPC or typed REST migration — is appropriate when the front-end and back-end are co-located in the same repo and the API surface is changing weekly. tRPC eliminates the duplicate-types problem entirely; the function signature on the server is the type the client gets. We don't recommend this for every project — tRPC pulls in opinions that some teams will not want to adopt — but for the right team it removes an entire category of regression. We will recommend it explicitly during the diagnostic if it fits your situation.
TypeScript refactor runbook and reference material
The documentation, CLIs, and specs we rely on for every TypeScript refactorengagement. We cite, we don't improvise.
- TypeScript handbook — Do's and Don'ts
The canonical reference for common type errors and idioms.
- TypeScript — strict mode
The compiler flag group we enable on every refactor.
- Zod — schema validation
Our default for IO boundary validation. Types are derived from schemas, not duplicated.
- Matt Pocock — Total TypeScript
The reference for advanced type patterns we apply case-by-case.
- typescript-eslint — recommended rules
The lint preset we install on every refactor engagement.
- React Hook Form + Zod resolver
Typed forms, derived from Zod, consistent with server-side validation.
- Next.js — typed routes
Experimental flag that types Link hrefs against your route tree.
TypeScript refactorrescues we've shipped
Related TypeScript refactor specialists
Related TypeScript refactor problems we rescue
TypeScript refactor questions founders ask
Sources cited in this dossier
Your AI builder shipped broken TypeScript refactor. We ship the fix.
Send the repo. We'll tell you exactly what's wrong in your TypeScript refactor layer — and the fixed price to ship it — in 48 hours.
Book free diagnostic →