afterbuild/ops
§ EX-04/typescript-refactor-expert
TypeScript refactor for AI apps

TypeScript refactor for AI apps — remove any types, ship a type-safe refactor of AI-generated code from Lovable, Bolt, v0 and Cursor

By Hyder Shah12 min readLast updated 2026-04-15
Quick verdict

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.

Source: TypeScript: Do's and Don'ts (official handbook)

§ MATRIX/ai-builder / failure-mode

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 builderWhat breaks in TypeScript refactorGo to
Lovabletsconfig.strict disabled, 'any' on every API response, no Zod validationLovable rescue
Bolt.newType casts to silence errors; preview and prod return different shapesBolt rescue
v0Component props typed as 'any' or untyped; no shared types packagev0 rescue
CursorBy file seven signatures drift; callers not updatedCursor regression loop
Replit AgentMixed JS/TS files, no root tsconfig, implicit any everywhereReplit rescue
Claude CodeGood at writing types when asked; rarely enables strict by defaultClaude Code rescue
WindsurfLarge codebase, stale types, type-only imports in wrong placesWindsurf rescue
Base44Limited TS surface; we migrate to Next.js + proper typesBase44 escape
§ ANATOMY/typescript refactor / failure

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.
Hyder Shah· TypeScript refactor rescue engineer

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.

§ RESCUE/typescript refactor / engagement

What a TypeScript refactor rescue engagement ships

From first diagnostic to production handoff — the explicit steps on every TypeScript refactor engagement.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

§ AUDIT/typescript refactor / first-pass

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.

  1. 01
    tsconfig.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.

  2. 02
    Instances 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.

  3. 03
    Declared return types on exported functions

    Inferred return types silently drift. Declared types force the compiler to verify the implementation matches the contract.

  4. 04
    Zod or Valibot at IO boundaries

    API responses, form submissions, webhook payloads, env vars. If untrusted data reaches the app without validation, we flag it.

  5. 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.

  6. 06
    Shared types package or types/ directory

    API and UI should share types. If the UI duplicates the API's shapes, they will drift. We consolidate.

  7. 07
    No-any, no-unsafe-* ESLint rules

    The typescript-eslint recommended-strict rule set, enforced in CI. Stops reintroduction of anys on future PRs.

  8. 08
    Discriminated unions for states

    Status types should be `'idle' | 'loading' | 'success' | 'error'` with narrowing. Boolean flags for complex state are a regression vector.

  9. 09
    Readonly props and readonly arrays where appropriate

    Catches accidental mutation, especially in React components.

  10. 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.

§ DIFF/typescript refactor / before-after

Common TypeScript refactor patterns we fix

These are the shapes AI-generated code arrives in — and the shape we leave behind.

The duplicated shared shape
✕ before · ai-shipped
tsx
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.
The duplicated shared shape
✓ after · afterbuild
tsx
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.
The duplicated shared shape
The any-typed API response
✕ before · ai-shipped
tsx
01`const res = await fetch('/api/orders'); const data: any = await res.json(); return data.orders.map(o => o.total);`
The any-typed API response
✓ after · afterbuild
tsx
01Zod schema for OrderResponse, parsed at the boundary, type inferred from schema, consumers receive fully typed data.
The any-typed API response
The union type that drifted
✕ before · ai-shipped
tsx
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.
The union type that drifted
✓ after · afterbuild
tsx
01Single OrderStatus defined once, re-exported. Exhaustive switch ensures every case is handled. Adding a status breaks the build until all sites are updated.
The union type that drifted
The silent as-cast
✕ before · ai-shipped
tsx
01`const user = auth.currentUser as User;` — user is actually `User | null`, app crashes on the first unauthenticated render path.
The silent as-cast
✓ after · afterbuild
tsx
01Explicit null check, narrowed type, exhaustive handling of the null case.
The silent as-cast
The untyped form
✕ before · ai-shipped
tsx
01Form state typed as `Record<string, any>`, submitted to an API route that accepts `any`, inserted into Postgres with no shape check.
The untyped form
✓ after · afterbuild
tsx
01Zod schema for the form, React Hook Form + zodResolver, server route parses with the same schema, Postgres insert fully typed.
The untyped form
Non-null assertion abuse
✕ before · ai-shipped
tsx
01`user!.name.toUpperCase()` everywhere. Any one null crashes the component.
Non-null assertion abuse
✓ after · afterbuild
tsx
01Proper optional chaining, default rendering when data is incomplete, narrowing guards.
Non-null assertion abuse
Any in the event handler
✕ before · ai-shipped
tsx
01`onClick={(e: any) => handleClick(e.target.value)}` — event target types lost, wrong element assumptions, runtime bugs when a label fires the handler.
Any in the event handler
✓ after · afterbuild
tsx
01Proper MouseEvent<HTMLButtonElement>, typed handler signature, compiler catches the mismatch.
Any in the event handler
The untyped env var
✕ before · ai-shipped
tsx
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.
The untyped env var
✓ after · afterbuild
tsx
01Typed env module parsing process.env with Zod at import. App refuses to boot with a clear error if any required var is missing.
The untyped env var
§ FLAGS/typescript refactor / red-signals

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.

flag
signal
why it matters
tsconfig.json has `strict: false` or missing compilerOptions entirely
The compiler isn't enforcing anything. Every refactor is a regression risk. The AI has no feedback loop.
More than 50 instances of `: any` or `as any` in the repo
Types are documentation, not contracts. The next AI prompt can break anything.
A types/ directory that exists but isn't imported anywhere
Types were defined, then abandoned. Working code drifted from its intended contract.
@ts-ignore or @ts-expect-error without a comment explaining why
Drive-by suppression. There's a real type error being hidden.
The same response shape typed differently in three files
The API's type is fiction. One of the three is wrong; at runtime, the user finds out which.
No ESLint configuration (or one that doesn't include @typescript-eslint)
Style and safety rules aren't enforced. Next PR reintroduces the anys you just killed.
tsc --noEmit reports 0 errors, but eslint reports 200 warnings
Weak tsconfig is hiding real problems. Strict mode will surface them immediately.
§ PRICING/typescript refactor / fixed-price

Fixed-price TypeScript refactor engagements

No hourly meter. Scope agreed up front, written fix plan, delivered on date.

price
Free
turnaround
Free rescue diagnostic
scope
30-min call + type-health report in 48 hours.
View scope
featured
price
From $1,999
turnaround
AI-generated code cleanup
scope
Strict mode, interfaces, Zod, lint rules.
View scope
price
$3,999
turnaround
Break the fix loop
scope
Two weeks — types + tests + architecture pass.
View scope
price
$7,499
turnaround
Finish my MVP
scope
Full productionisation with strict TS throughout.
View scope
§ EXAMPLES/typescript refactor / real-scopes

What TypeScript refactor rescues actually cost

Anonymized, representative scopes from recent TypeScript refactor rescues. Every price is the one we actually quoted.

Small rescue
$1,999

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
Medium rescue
$3,999

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
Large rescue
$7,499

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
§ DECISION/typescript refactor / migrate-or-patch

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.

§ RUNBOOK/typescript refactor / references

TypeScript refactor runbook and reference material

The documentation, CLIs, and specs we rely on for every TypeScript refactorengagement. We cite, we don't improvise.

§ FAQ/typescript refactor / founders-ask

TypeScript refactor questions founders ask

FAQ
Can a TypeScript refactor for AI apps enable strict mode without everything exploding?
Not in one commit — you'd have thousands of errors. We enable strict at the tsconfig level, then remove any types domain by domain behind per-file type: checks. Each PR stays green in CI. A type-safe refactor takes one to three weeks depending on repo size. The strategy is exactly what the TypeScript team recommends for migrating large JavaScript codebases — incremental, reviewable, never regressing.
Is an AI-generated code refactor just cosmetic, or does it actually prevent bugs?
It prevents exactly the regression loop bugs. When the AI changes a function and forgets a caller, strict TS fails the build. Our customers see a measurable drop in 'the AI broke working features' incidents after a refactor pass. One customer tracked the metric formally — 4 incidents per week before, zero in the six weeks after — and the engagement paid for itself in time-saved alone.
Will the AI coding tool still work after a type-safe refactor?
Yes, better. Strict types give Lovable, Cursor and Claude Code more signal. We ship .cursorrules and CLAUDE.md files that tell the AI to respect the types. Prompts become more reliable. Several customers have remarked that their AI builder produces visibly better code after the type-safe refactor — which makes sense; the model has more scaffolding to align against.
Does the TypeScript refactor for AI apps use Zod, Valibot, or io-ts?
Zod by default — biggest ecosystem, works with tRPC, React Hook Form, Next.js Route Handlers. Valibot when bundle size matters. We pick per project. Both libraries are excellent; the choice is usually about which other tools the project already uses.
What about libraries without types during the AI-generated code refactor?
We write minimal type declarations for the surface you use. Not full DefinitelyTyped contributions — just enough to make your code safe. If a library is core to the product and has no types at all, we will sometimes recommend swapping to a typed equivalent.
Can you handle Next.js 16 strict typing in a type-safe refactor?
Yes — we follow the Next.js 16 docs in node_modules/next/dist/docs/ for the exact current APIs. Route handlers, Server Components, Server Actions, all properly typed after the refactor.
What's the ROI of a TypeScript refactor for AI apps?
You stop shipping regressions. One of our clients logged 4 'AI broke working feature' incidents per week before the refactor, zero in the six weeks after. That alone pays for the engagement. The harder-to-measure return is that the team starts trusting the AI again — a year of regressions teaches a founder to read every diff suspiciously, and strict types restore enough confidence to delegate again.
Will tests still pass during a type-safe refactor?
Yes — we do not touch behaviour. The refactor is type-only; runtime semantics are preserved. We do, however, frequently uncover bugs the old types were hiding (a function that was 'returning a User but actually returning null sometimes'), and we fix those as we go with explicit code changes that are reviewed separately.
What if my repo mixes TypeScript and JavaScript during the AI-generated code refactor?
Common in older AI-built apps. We typically migrate the JavaScript files to TypeScript as part of the engagement — `allowJs` plus per-file conversion, starting with the most-edited files. The conversion itself is mechanical for most files; the hard part is fixing the type errors that surface, and that work overlaps with the remove-any-types pass we are already doing.
Next step

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 →