Tinker AI
Read reviews

Outcome

Migration shipped in 8 weeks vs estimated 14 weeks; Aider handled ~60% of the rewrite mechanically; ~$340 in API costs

13 min read

Last quarter I led the migration of an internal admin tool from a jQuery codebase to React. The app was small enough to be tractable (23k lines of JavaScript) and old enough to be painful (originally written in 2014, jQuery 1.11, no module system, lots of inline event handlers). My initial estimate was 14 weeks. The actual was 8 weeks. The difference was largely Aider, used carefully on the mechanical parts.

This is the breakdown — what Aider handled well, where I had to do it myself, and what I’d do differently next time.

The starting point

The codebase:

  • 23,000 lines of JavaScript across 47 files
  • jQuery 1.11, Backbone for routing, Bootstrap 3 for styling
  • Server-rendered HTML templates with progressive enhancement
  • About 180 event handlers, many inline in HTML
  • ~30% test coverage in QUnit, mostly assertion-style tests for utility functions
  • Used by 12 internal users for daily admin tasks

The target:

  • React 18 with TypeScript
  • React Router for routing
  • Existing backend API stays unchanged (the migration is frontend-only)
  • shadcn/ui for components
  • Vitest + React Testing Library
  • Coverage target: 60%+ on critical paths

Time pressure: real but not crushing. Six weeks of full-time work was budgeted; we had room to slip.

The setup

I used Aider 0.55 with Claude 3.5 Sonnet via Anthropic API. Configuration:

# .aider.conf.yml
model: claude-3-5-sonnet-20241022
read:
  - CONVENTIONS.md
  - docs/migration-plan.md
auto-commits: true
gitignore: true

The CONVENTIONS.md was 60 lines covering React patterns we wanted (functional components, hooks-based state, no class components, specific TypeScript styles).

The migration-plan.md was a 200-line document describing how the rewrite was organized — what got migrated first, what shape the React equivalents should take, naming conventions for the new files.

Both files were loaded as read-only in every Aider session, which kept the model on script across the dozens of sessions over 8 weeks.

The rough phases

I broke the migration into phases:

  1. Setup: scaffold the React app, set up build, get a “hello world” rendering (week 1)
  2. Utilities: port pure utility functions (no DOM, no jQuery dependency) (week 1)
  3. Stateless components: port leaf-level UI pieces that don’t have business logic (weeks 2-3)
  4. Stateful components: port pieces that manage local state (weeks 3-5)
  5. Routes and pages: assemble the components into route-level views (weeks 5-7)
  6. Cleanup: remove dead jQuery code, update tests, polish (week 8)

Aider’s leverage varied dramatically by phase.

Phase-by-phase, with numbers

Phase 1: Setup (week 1)

Aider involvement: minimal. Setting up Vite, configuring TypeScript, getting the build working — these are tasks where the model would pretend to know things it doesn’t (specific Vite config flags change between versions). I did this manually.

Time: 3 days. Token cost: ~$5. Aider value: low.

Phase 2: Utilities (week 1)

Aider involvement: high. The pattern was: “Here’s a jQuery utility function. Port it to TypeScript with no jQuery dependency. Use the existing test as the spec.”

> /add src/utils/dateHelpers.js src/utils/dateHelpers.test.js src-new/utils/dateHelpers.ts

> Port dateHelpers.js to TypeScript. The new file should have no jQuery dependency.
> Use the existing tests in dateHelpers.test.js as the specification — the new code
> must pass those tests with minimal modification (just the import path).

About 35 utility functions across 6 files. Aider handled 31 of them cleanly with one prompt each. 4 needed back-and-forth because the original used jQuery-specific behaviors (like $.extend with deep merge semantics) that don’t have direct standard-library equivalents. For those, I described the desired behavior precisely and Aider produced clean output.

Time: 2 days. Token cost: ~$25. Aider value: high — this would have been 4-5 days by hand.

Phase 3: Stateless components (weeks 2-3)

Aider involvement: high. The pattern was: “Here’s a Backbone view with rendering logic. Convert to a React functional component with props matching the data it consumed.”

> /add src/views/UserCard.js src-new/types/user.ts

> Convert this Backbone view to a React functional component in
> src-new/components/UserCard.tsx. The component should take a User prop
> (type defined in user.ts) and render the same DOM structure. Use Tailwind
> classes for styling instead of the existing Bootstrap classes — match the
> visual result, not the markup.

This worked startlingly well. Backbone views with simple render methods translate cleanly to React. Aider produced output that was 80%+ ready to use, with the remaining 20% being CSS-class adjustments for the visual match.

About 25 components in this phase. Average time per component with Aider: 25 minutes. Without Aider, my estimate based on similar work I’ve done by hand: 75-90 minutes per component. The savings here drove most of the schedule advantage.

Time: 8 days for 25 components. Token cost: ~$80. Aider value: very high.

Phase 4: Stateful components (weeks 3-5)

Aider involvement: medium. Components with state and effects were trickier. The Backbone equivalents had patterns like “this view listens to model events and re-renders” — there’s no direct mapping to React, and Aider would sometimes produce code that worked but used effects in non-idiomatic ways.

> /add src/views/UserListEditor.js src-new/types/user.ts

> Convert this Backbone view to a React component. The view manages a list of
> users that can be added/removed. Currently it listens to a Backbone collection
> for changes. In the React version, manage the state with useState and pass an
> onChange callback to the parent. Don't use a global state library.

Aider’s first output for these was usually 60% right. The bugs were subtle: an effect that should have run on mount only ran on every render, a state update that mutated an array instead of replacing it, a stale closure issue that compiled fine and broke at runtime.

For these components, I’d accept the Aider draft, run it, find the bug by manual testing, then ask Aider to fix the specific issue. Two or three iterations per component. Final code was usually correct but I had to be the one finding the bugs.

About 18 components in this phase. Average time per component: 45 minutes. Without Aider: probably 90 minutes (the React work is the slow part, Aider scaffolds it but doesn’t get it right first time).

Time: 9 days for 18 components. Token cost: ~$110. Aider value: moderate.

Phase 5: Routes and pages (weeks 5-7)

Aider involvement: low. Assembling components into pages required cross-cutting decisions — how routing works, where loading states live, how authentication propagates, how forms submit. These are decisions Aider couldn’t make for me, and the work was mostly thinking-through rather than typing.

I used Aider for specific small tasks: “Wire this form to call this API endpoint with optimistic UI.” It handled those fine. The architectural assembly I did myself.

Time: 12 days. Token cost: ~$60. Aider value: low to moderate.

Phase 6: Cleanup (week 8)

Aider involvement: high. The cleanup phase was largely mechanical: remove dead jQuery code, update tests to use Vitest patterns, fix any remaining type errors, polish styling.

> /add src/legacy/oldThing.js

> This file is no longer imported anywhere. Confirm by searching the project,
> then delete it.

This kind of mechanical cleanup is exactly what Aider is good at. Most of the cleanup phase was 2-3 minute Aider sessions on individual files.

Time: 3 days. Token cost: ~$25. Aider value: high.

The totals

PhaseTimeCostAider value
Setup3 days$5Low
Utilities2 days$25High
Stateless components8 days$80Very high
Stateful components9 days$110Moderate
Routes and pages12 days$60Low-moderate
Cleanup3 days$25High
Total37 working days (~8 weeks)~$305Moderate-high

Final API spend was a bit higher (~$340) due to abandoned sessions and false starts not captured in the per-phase numbers.

The original estimate of 14 weeks would have been about right without Aider. Shipping in 8 weeks was a 43% timeline reduction, which sounds like a marketing claim until you look at the per-phase breakdown — most of the gain was in the two phases (utilities and stateless components) where the work was mechanical and Aider’s output was nearly final.

What I’d do differently

Use —architect mode for the stateful components phase. I used straight Sonnet for the whole project. In retrospect, the stateful components phase had a lot of “plan the conversion, then execute” steps where architect mode (Sonnet plans, Haiku edits) would have saved tokens without sacrificing quality.

Write tests first for the stateful components. I let Aider write the components, then wrote tests after. About 4 of the 18 components had subtle bugs that tests would have caught earlier. For the next migration, test-first.

Don’t try to use Aider on architectural assembly. I burned 4-5 hours over the routes phase trying to get Aider to make routing decisions. It can’t. Save the time, do it yourself.

Keep the CONVENTIONS.md tighter. My initial version was 100 lines. I trimmed to 60 around week 3. The shorter version produced better adherence. Should have started shorter.

Use Aider’s /test command. I didn’t know about it until late in the project. It runs your test command and feeds the output back into the conversation, so Aider can iterate on failing tests. This would have saved me the manual “run tests, paste failures, ask for fix” loop.

The honest summary

This migration shipped in 8 weeks instead of 14. Aider was real leverage on roughly 60% of the work. The other 40% — setup, architecture, decisions about how things should fit together — was unchanged from how I’d have done it without AI tools.

The $340 in API costs is small relative to engineering time saved. By any normal accounting, this was a strong case for AI-assisted migration.

Two qualifications. First, this was my own project — I knew the source codebase intimately and could write precise prompts. The same migration handed to a developer unfamiliar with the source would have benefited less from Aider, because the prompts would have been less specific.

Second, the tool I used (Aider with auto-commits, careful CONVENTIONS.md, manual review of every diff) is closer to “AI as power tool” than “AI does the work for me.” The work was still mine; Aider made the typing faster. That’s a different thing from autonomous code generation, and it’s where the productivity gains actually showed up.

If your migration profile looks like mine — small enough to be tractable, mechanical enough that the work is mostly translation rather than redesign — Aider can compress the timeline meaningfully. If your migration involves redesigning the architecture, expect Aider to help with about a third of the work and not the hard third.