DEV Community

Sergio Azócar
Sergio Azócar

Posted on • Originally published at sergioazocar.com

How to migrate with Claude Code and not die trying

Migrating no longer hurts

I migrated the entire design of this site in a couple of days. Before Claude Code, that same migration would have eaten up two weeks of my time, and I'd probably have put it off for six months out of pure laziness.

I've been doing migrations for years. Vue 2 to Vue 3, full design systems, architectural refactors that touched hundreds of files. They all had one thing in common, they hurt. Long, mechanical projects with high risk and little visible reward. The kind of work you know has to be done but keep putting off because it sounds like hell.

Not anymore.

The historical pain of migrating

Migrating from Vue 2 to Vue 3 was a quarter-long project. Not because of real technical difficulty (most of it was replacing deprecated APIs), but because of sheer volume. Hundreds of components, each with its own quirks, all of them needed touching. Add Pinia, Vue Router 4, the third-party plugins that didn't yet support v3. And while you were migrating, the rest of the team kept merging features into main. By the time you tried to merge back, it was hell.

Migrating a design system was similar but worse. You change the token system, the base components, the prop names. Suddenly every <Button variant="primary" /> in the app needs review, and when you have hundreds of those, no team signs off on a full quarter just for that.

The pattern was always the same, high load, low motivation, indefinite procrastination.

Why migrations are the perfect scenario for an agent

After doing several with Claude Code, I've identified three conditions where they're ideal:

  1. High volume of mechanical changes. The oxlint PR at my current job touched more than 700 files. Most were trivial adjustments (import order, equivalent patterns). Exactly the kind of work that is humanly tedious and that an agent does in minutes.

  2. Clear, automatable feedback. Linter, Typecheck, Tests, they pass or they don't. The Build compiles or it doesn't. Every step is a loop with binary response, exactly the kind of signal an agent needs to iterate without continuous supervision.

  3. Accessible documentation. The target tool almost always has a migration guide. Pass the guide to the agent, give it the project context, and it stops making things up.

If your task meets all three, stop procrastinating. It's work for an agent.

Case 1: a monorepo's toolchain

The team at my current job operated a monorepo with 7 applications on Cloudflare Workers, around 1,670 TypeScript files managed with pnpm workspaces. The quality pipeline used Biome for lint and format, and tsc for type-check.

Two bottlenecks:

  • Biome and tsc required NODE_OPTIONS='--max-old-space-size=8192' to avoid out of memory crashes during CI.
  • tsc, single-threaded, took 25 seconds to typecheck the entire monorepo.

We'd been watching for months what the oxc team (oxlint, oxfmt, everything they're building in Rust) was shipping. The question wasn't whether the migration was worth it, but how much effort it was going to cost.

Migration to oxlint + oxfmt

The steps:

  1. Replace Biome with oxlint and oxfmt.
  2. Configure .oxlintrc.json with 129 active rules.
  3. Configure an equivalent .oxfmtrc.json.
  4. Update scripts in package.json.
  5. Adjust the GitHub Actions pipeline.
  6. Adjust the editor config.

The bulk of the work wasn't switching tools. It was adjusting code to meet the new rules (mostly about import order and equivalent patterns). Claude Code made the changes, we ran the validations, adjusted, repeated.

Metric Biome oxlint (Jan 2026) oxlint (Mar 2026)
Average time 3.70s 1.04s 0.49s
Files analyzed 1,227 1,262 ~1,670

oxlint was processing more files with more rules in a fraction of the time. And the tool keeps improving with every release.

Migration to tsgo

TypeScript 7 (tsgo) is the rewrite of tsc (TypeScript Compiler) in Go. Microsoft promised 7x to 10x improvements. The migration was even more straightforward:

  1. Install @typescript/native-preview.
  2. Change the script from NODE_OPTIONS='--max-old-space-size=8192' tsc to tsgo.
  3. Adjust tsconfig.json (drop baseUrl, add ./ as prefix in paths).
  4. Update the CI cache (from .tsbuildinfo to .tsgo-cache).
  5. Fix types in places where tsgo is stricter than tsc.

The bonus, you no longer need the memory config. tsgo runs in Go without Node's constraints.

Metric tsc tsgo (Jan 2026) tsgo (Mar 2026)
Time (cold) 25.34s 5.32s 0.76s
CPU usage ~132% ~430% ~430%
Requires memory config Yes (8 GB) No No

33 times faster on typecheck, without touching a single line of TypeScript code in the project.

The final number

Phase Before Migration Today (Apr 2026)
Lint 3.70s 1.04s 0.49s
Typecheck 25.34s 5.32s 0.76s
Total 29.04s 6.36s 1.25s

From 29 seconds to 1.25. 23 times faster. And what finally convinced the team were precisely those results. Without measuring before and after, the migration is just "Sergio changed the tools". With the results, it's "lint and typecheck are 23X faster".

Lesson: always measure. That's what turns a migration into a story the team understands and signs off on.

Case 2: screaming architecture in a large frontend

Before the toolchain migration, I'd done another one: applying screaming architecture to the main product's frontend. Reorganizing the repo from technical folders (components/, services/, store/) into feature modules (modules/Auth/, modules/Orders/, etc).

The change itself is simple: move files and update imports. But when it's hundreds of files in hundreds of imports, it's the textbook definition of "mechanical work nobody wants to do on a Friday".

This is where Claude Code shines. The pattern is uniform, changes are verifiable (compiles or doesn't), and the only new rule is "everything in feature X lives in modules/X/". The agent gets the pattern from the first example and applies it everywhere else.

All I had to do was define the contract (specs). Which modules to create, what counts as shared, how to handle files that touched more than one feature. After that it was iterate, validate build, validate tests, adjust.

Lesson: structural refactors are ideal when the pattern is clear but the volume is high. Define the rules once, let the agent apply them everywhere.

Case 3: the redesign of this site

The most recent one and the closest to this post. Migrating sergioazocar.com from the previous design to the current one: edge-to-edge layout with BentoGrid, Nuxt UI v4, Tailwind v4, custom color system (beacon and orbit), dark-only theme, translucent borders with color-mix.

The difference with the previous cases is that the project context was entirely mine. I have a well-kept CLAUDE.md, with the repo conventions, the stack, the i18n rules, the blog post patterns, the gotchas. When I ask Claude Code to migrate a component to the new border system, I don't need to explain what border-(muted) is or where it's defined. It already knows.

That's dogfooding. The project is ready for the agent to be useful from the first prompt. And that pays dividends fast, every turn moves forward instead of being spent re-explaining context.

Lesson: investing in a good CLAUDE.md isn't overhead, it's the difference between a useful agent and one that hallucinates.

The playbook

After several migrations, this is what I always do:

  1. Plan before generating. A poorly scoped migration is a failed migration. Define the scope, the steps, the files. If you don't have clarity before starting, the agent won't either.

  2. Atomic PRs per phase. In the toolchain case, oxlint and tsgo were separate PRs. If something breaks, you know exactly which phase caused it.

  3. Validate every step. Lint, type-check, tests, preview. Claude Code runs the commands, you read the output. Skipping validation because "the previous change passed" is the recipe for piling up 50 errors before you notice.

  4. Keep a CLAUDE.md up to date. Stack, conventions, commands, project gotchas. The difference between an agent that understands the repo and one that makes things up.

  5. Measure before and after. Without numbers there's no story to tell, nor argument to pitch to the team.

  6. Pair programming, not autopilot. The agent doesn't replace judgment, it amplifies it. You decide what gets done, it runs and brings the feedback.

The tools I use in a migration

After several migrations, there's a setup I repeat. These are the pieces that make the difference between useful Claude Code and Claude Code that wastes your time.

A CLAUDE.md specific to the current context. Conventions, commands, gotchas, and the migration in progress. Don't copy package.json (Claude Code can read it already), write what isn't inferable from the deps. Minimal example:

# Project name

## Relevant stack (what isn't inferable from package.json)

- Deployed to Cloudflare Workers
- i18n with prefix strategy (ES/EN)
- Composition API only, no Options API
- oxlint instead of eslint

## Commands

- `pnpm lint` — oxlint
- `pnpm test` — vitest

## Migration in progress

Biome → oxlint + oxfmt. Replace each rule with its equivalent,
run `pnpm lint` after every batch, don't touch tests in this PR.
Enter fullscreen mode Exit fullscreen mode

/plan before generating. Makes the agent investigate the repo, propose a structured plan, and only execute after you approve it. Perfect for migrations, it saves you from starting three times because the first scope was wrong.

Sub-agents to parallelize. While the main agent keeps editing, launch a sub-agent that runs pnpm typecheck or pnpm test on another module and brings back the result. For large migrations, 2-3 sub-agents in parallel split the work and keep the main agent's context clean.

Hooks for automatic validation. A PostToolUse hook that runs pnpm lint after every Edit is the cheapest way to stop having to ask "validate" every time. It runs on its own, if it fails the agent sees it and fixes it on the next turn.

What NOT to do

  • Don't ask for everything at once. "Migrate the whole repo" is the worst possible instruction. Split into phases, one at a time.

  • Don't skip validation. Even if the change looks trivial, run the checks. The error you assume doesn't exist is the one that ends up in production.

  • Don't let it invent paths or APIs. If it cites a function you don't recognize, verify it exists before accepting the change.

  • Don't merge without understanding the changes. The agent accelerates your work, it doesn't replace your responsibility. If you don't understand what changed, you're not migrating, you're crossing your fingers.

Closing

The barrier to entry for migrating dropped dramatically. What used to be a quarterly project is now a weekend project. What you used to push back six months, you can start today and finish in a couple of days.

If you have a migration that's been stuck for a while, open Claude Code this week. You'll probably finish it before the sprint ends.

And even if the final metrics don't come out that pretty, you'll still understand more about your codebase in two days than in the six months you'd been putting it off.

Top comments (0)