[{"data":1,"prerenderedAt":1406},["ShallowReactive",2],{"search-en":3,"blog-how-to-migrate-with-claude-code-and-not-die-trying-en":272},[4,10,16,21,26,31,37,42,47,52,57,62,67,72,77,82,87,92,97,102,107,112,117,122,127,132,137,142,147,152,157,162,167,172,177,182,187,192,197,202,207,212,217,222,227,232,237,242,247,252,257,262,267],{"id":5,"title":6,"titles":7,"content":8,"level":9},"\u002Fen\u002Fblog\u002Fhow-to-migrate-with-claude-code-and-not-die-trying","How to migrate with Claude Code and not die trying",[],"Three real migrations with Claude Code: monorepo toolchain, screaming architecture, and a full site redesign. What used to take weeks, now takes days.",1,{"id":11,"title":12,"titles":13,"content":14,"level":15},"\u002Fen\u002Fblog\u002Fhow-to-migrate-with-claude-code-and-not-die-trying#migrating-no-longer-hurts","Migrating no longer hurts",[6],"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.",2,{"id":17,"title":18,"titles":19,"content":20,"level":15},"\u002Fen\u002Fblog\u002Fhow-to-migrate-with-claude-code-and-not-die-trying#the-historical-pain-of-migrating","The historical pain of migrating",[6],"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 \u003CButton variant=\"primary\" \u002F> 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.",{"id":22,"title":23,"titles":24,"content":25,"level":15},"\u002Fen\u002Fblog\u002Fhow-to-migrate-with-claude-code-and-not-die-trying#why-migrations-are-the-perfect-scenario-for-an-agent","Why migrations are the perfect scenario for an agent",[6],"After doing several with Claude Code, I've identified three conditions where they're ideal: 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.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.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.",{"id":27,"title":28,"titles":29,"content":30,"level":15},"\u002Fen\u002Fblog\u002Fhow-to-migrate-with-claude-code-and-not-die-trying#case-1-a-monorepos-toolchain","Case 1: a monorepo's toolchain",[6],"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.",{"id":32,"title":33,"titles":34,"content":35,"level":36},"\u002Fen\u002Fblog\u002Fhow-to-migrate-with-claude-code-and-not-die-trying#migration-to-oxlint-oxfmt","Migration to oxlint + oxfmt",[6,28],"The steps: Replace Biome with oxlint and oxfmt.Configure .oxlintrc.json with 129 active rules.Configure an equivalent .oxfmtrc.json.Update scripts in package.json.Adjust the GitHub Actions pipeline.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. MetricBiomeoxlint (Jan 2026)oxlint (Mar 2026)Average time3.70s1.04s0.49sFiles analyzed1,2271,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.",3,{"id":38,"title":39,"titles":40,"content":41,"level":36},"\u002Fen\u002Fblog\u002Fhow-to-migrate-with-claude-code-and-not-die-trying#migration-to-tsgo","Migration to tsgo",[6,28],"TypeScript 7 (tsgo) is the rewrite of tsc (TypeScript Compiler) in Go. Microsoft promised 7x to 10x improvements. The migration was even more straightforward: Install @typescript\u002Fnative-preview.Change the script from NODE_OPTIONS='--max-old-space-size=8192' tsc to tsgo.Adjust tsconfig.json (drop baseUrl, add .\u002F as prefix in paths).Update the CI cache (from .tsbuildinfo to .tsgo-cache).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. Metrictsctsgo (Jan 2026)tsgo (Mar 2026)Time (cold)25.34s5.32s0.76sCPU usage~132%~430%~430%Requires memory configYes (8 GB)NoNo 33 times faster on typecheck, without touching a single line of TypeScript code in the project.",{"id":43,"title":44,"titles":45,"content":46,"level":36},"\u002Fen\u002Fblog\u002Fhow-to-migrate-with-claude-code-and-not-die-trying#the-final-number","The final number",[6,28],"PhaseBeforeMigrationToday (Apr 2026)Lint3.70s1.04s0.49sTypecheck25.34s5.32s0.76sTotal29.04s6.36s1.25s From 29 seconds to 1.25. 23 times faster. And what finally convinced the team were precisely those numbers. Without measuring before and after, the migration is just \"Sergio changed the tools\". With the numbers, it's \"code quality feedback is 23X faster\". Lesson: always measure. That's what turns a migration into a story the team understands and signs off on.",{"id":48,"title":49,"titles":50,"content":51,"level":15},"\u002Fen\u002Fblog\u002Fhow-to-migrate-with-claude-code-and-not-die-trying#case-2-screaming-architecture-in-a-large-frontend","Case 2: screaming architecture in a large frontend",[6],"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\u002F, services\u002F, store\u002F) into feature modules (modules\u002FAuth\u002F, modules\u002FOrders\u002F, 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\u002FX\u002F\". 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.",{"id":53,"title":54,"titles":55,"content":56,"level":15},"\u002Fen\u002Fblog\u002Fhow-to-migrate-with-claude-code-and-not-die-trying#case-3-the-redesign-of-this-site","Case 3: the redesign of this site",[6],"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.",{"id":58,"title":59,"titles":60,"content":61,"level":15},"\u002Fen\u002Fblog\u002Fhow-to-migrate-with-claude-code-and-not-die-trying#the-playbook","The playbook",[6],"After several migrations, this is what I always do: 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.Atomic PRs per phase. In the toolchain case, oxlint and tsgo were separate PRs. If something breaks, you know exactly which phase caused it.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.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.Measure before and after. Without numbers there's no story to tell, nor argument to pitch to the team.Pair programming, not autopilot. The agent doesn't replace judgment, it amplifies it. You decide what gets done, it runs and brings the feedback.",{"id":63,"title":64,"titles":65,"content":66,"level":15},"\u002Fen\u002Fblog\u002Fhow-to-migrate-with-claude-code-and-not-die-trying#what-not-to-do","What NOT to do",[6],"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.",{"id":68,"title":69,"titles":70,"content":71,"level":15},"\u002Fen\u002Fblog\u002Fhow-to-migrate-with-claude-code-and-not-die-trying#closing","Closing",[6],"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.",{"id":73,"title":74,"titles":75,"content":76,"level":9},"\u002Fen\u002Fblog\u002Foxlint-tailwindcss-the-linting-plugin-tailwind-v4-needed","oxlint-tailwindcss: the linting plugin Tailwind v4 needed",[],"The most complete linting plugin for Tailwind CSS, native to oxlint. 22 rules with autofix, zero-config and designed from scratch for Tailwind CSS v4.",{"id":78,"title":79,"titles":80,"content":81,"level":15},"\u002Fen\u002Fblog\u002Foxlint-tailwindcss-the-linting-plugin-tailwind-v4-needed#from-problem-to-open-source-the-perfect-opportunity-to-contribute","From problem to open-source: the perfect opportunity to contribute",[74],"The day the oxc team opened the alpha for native oxlint plugins, it was the perfect excuse to solve a problem I'd been having at my current job for a while — linting Tailwind CSS classes with oxlint. If you use Tailwind CSS v4 with oxlint, the existing linting options aren't built for that combo. eslint-plugin-tailwindcss is solid but lives in the ESLint world and its v4 support is still partial. eslint-plugin-better-tailwindcss works in oxlint through the jsPlugins compatibility layer, and it gets the job done — but it's not a native plugin and its rules are more limited. Neither was designed specifically for oxlint + Tailwind CSS v4. So I built it.",{"id":83,"title":84,"titles":85,"content":86,"level":15},"\u002Fen\u002Fblog\u002Foxlint-tailwindcss-the-linting-plugin-tailwind-v4-needed#what-is-oxlint-tailwindcss","What is oxlint-tailwindcss",[74],"A native oxlint plugin with 22 linting rules designed exclusively for Tailwind CSS v4. It's not an ESLint port or a wrapper, it uses the @oxlint\u002Fplugins API directly. Only 2 runtime dependencies. This matters because being native means it shares the same parsing cycle as oxlint. There's no interoperability overhead, no translation layer. It's as fast as oxlint itself, and it shows.",{"id":88,"title":89,"titles":90,"content":91,"level":36},"\u002Fen\u002Fblog\u002Foxlint-tailwindcss-the-linting-plugin-tailwind-v4-needed#works-without-configuration","Works without configuration",[74,84],"The plugin auto-detects your Tailwind CSS entry point. If your file is called app.css, globals.css, main.css, tailwind.css (or any of the conventional names) and contains @import \"tailwindcss\", it finds it automatically. {\n  \"jsPlugins\": [\"oxlint-tailwindcss\"],\n  \"rules\": {\n    \"tailwindcss\u002Fno-unknown-classes\": \"error\",\n    \"tailwindcss\u002Fno-conflicting-classes\": \"error\",\n    \"tailwindcss\u002Fenforce-sort-order\": \"warn\"\n  }\n} The auto-detection follows @import statements one level deep — including package imports like @import '@company\u002Ftheme\u002Ftailwind.config.css'. In monorepos, the search stops at package.json boundaries so each package resolves its own design system automatically. The design system is loaded once per entry point, cached in memory and on disk. In a monorepo with multiple packages sharing the same entry point, the design system is loaded only once. If you have a non-conventional entry point you can set it in settings.tailwindcss.entryPoint or per rule.",{"id":93,"title":94,"titles":95,"content":96,"level":15},"\u002Fen\u002Fblog\u002Foxlint-tailwindcss-the-linting-plugin-tailwind-v4-needed#_22-rules-in-four-categories","22 rules in four categories",[74],"",{"id":98,"title":99,"titles":100,"content":101,"level":36},"\u002Fen\u002Fblog\u002Foxlint-tailwindcss-the-linting-plugin-tailwind-v4-needed#correctness-catch-real-bugs","Correctness — Catch real bugs",[74,94],"Correctness rules catch bugs before they reach the browser. no-unknown-classes detects classes that don't exist in your design system and suggests fixes for typos: \u003Cdiv className=\"flex itms-center bg-blu-500\" \u002F>\n\u002F\u002F                   ^^^^^^^^^^^\n\u002F\u002F \"itms-center\" is not a valid Tailwind CSS class.\n\u002F\u002F Did you mean \"items-center\"? It supports allowlist to allow custom classes not in your design system and ignorePrefixes to skip prefixes that aren't Tailwind classes. no-conflicting-classes tells you exactly which CSS property is conflicting and which class wins: \u003Cdiv className=\"text-red-500 text-blue-500\" \u002F>\n\u002F\u002F \"text-red-500\" and \"text-blue-500\" affect \"color\".\n\u002F\u002F \"text-blue-500\" takes precedence (appears later). no-dark-without-light detects when you use dark: without a base class, something that often causes missing styles in light mode: \u002F\u002F ❌ — what background does it have in light mode?\n\u003Cdiv className=\"dark:bg-gray-900\" \u002F>\n\n\u002F\u002F ✅\n\u003Cdiv className=\"bg-white dark:bg-gray-900\" \u002F> no-dark-without-light checks dark: by default, but the variants option lets you enforce the same pattern for any variant — useful if your project uses custom variants. no-contradicting-variants catches redundant variants where the base class already applies unconditionally: \u002F\u002F ❌ — dark:flex is redundant, flex already applies always\n\u003Cdiv className=\"flex dark:flex\" \u002F> no-deprecated-classes automatically replaces classes deprecated in v4: \u002F\u002F ❌ v3\n\u003Cdiv className=\"flex-grow overflow-ellipsis decoration-slice\" \u002F>\n\n\u002F\u002F ✅ v4 (autofix)\n\u003Cdiv className=\"grow text-ellipsis box-decoration-slice\" \u002F> Also flex-shrink → shrink and decoration-clone → box-decoration-clone. Plus the usual no-duplicate-classes (with autofix) and no-unnecessary-whitespace.",{"id":103,"title":104,"titles":105,"content":106,"level":36},"\u002Fen\u002Fblog\u002Foxlint-tailwindcss-the-linting-plugin-tailwind-v4-needed#style-team-consistency","Style — Team consistency",[74,94],"enforce-sort-order sorts classes according to the official Tailwind CSS order (with autofix), compatible with oxfmt and prettier-plugin-tailwindcss. Its strict mode groups classes by variant prefix, sorts within each group, and orders groups by variant priority. enforce-shorthand converts mt-2 mr-2 mb-2 ml-2 to m-2, w-full h-full to size-full, and many more combinations. All with autofix. enforce-logical converts physical properties to logical ones for LTR\u002FRTL support: ml-4 → ms-4, left-0 → start-0. Its inverse, enforce-physical, does the opposite for projects that are LTR-only and prefer consistency with physical properties. Both with autofix. enforce-consistent-variable-syntax normalizes CSS variable syntax between bg-[var(--primary)] and v4's shorthand bg-(--primary). enforce-canonical converts arbitrary values to native classes when they exist: p-[2px] → p-0.5 (uses rootFontSize of 16px by default for conversion). It plugs directly into Tailwind's canonicalization API. enforce-consistent-important-position (default suffix, v4's canonical form), enforce-negative-arbitrary-values (-top-[5px] → top-[-5px]), and consistent-variant-order round out the style rules.",{"id":108,"title":109,"titles":110,"content":111,"level":36},"\u002Fen\u002Fblog\u002Foxlint-tailwindcss-the-linting-plugin-tailwind-v4-needed#complexity-keep-code-manageable","Complexity — Keep code manageable",[74,94],"max-class-count warns when an element exceeds 20 classes (configurable). It's the signal that it's time to extract a component. enforce-consistent-line-wrapping controls the class string length by print width or by number of classes per line.",{"id":113,"title":114,"titles":115,"content":116,"level":36},"\u002Fen\u002Fblog\u002Foxlint-tailwindcss-the-linting-plugin-tailwind-v4-needed#restrictions-design-system-rules","Restrictions — Design system rules",[74,94],"no-hardcoded-colors forbids hardcoded colors like bg-[#ff5733] in arbitrary brackets — the typical shortcut that erodes your design system. no-arbitrary-value and no-unnecessary-arbitrary-value (with autofix) control the use of arbitrary values. The latter detects when you use h-[auto] but h-auto exists. no-restricted-classes allows blocking specific classes by name or regex, with custom messages.",{"id":118,"title":119,"titles":120,"content":121,"level":15},"\u002Fen\u002Fblog\u002Foxlint-tailwindcss-the-linting-plugin-tailwind-v4-needed#class-extraction","Class extraction",[74],"The parser is what makes all of this work reliably. It's not a regex that looks for className= and hopes for the best. It extracts classes from: JSX attributes (className, class)Object-valued JSX attributes — e.g. Mantine's \u003CInput classNames={{ root: \"flex\", input: \"border-none\" }} \u002F>Template literals with interpolationTernariesUtility functions: cn(), clsx(), cx(), cva(), twMerge(), twJoin(), and moreFull cva() — base, variants, compoundVariantsFull tv() — base, slots, variants with slot objects, compoundSlotsclassed() (tw-classed) — skips element type, extracts classes and cva-like configTagged templates (tw\\...``)Variables by name (className, classes, style, styles)Component classes defined with @layer components { .btn {} } in your CSS It handles nested brackets, nested calc, arbitrary variants, quoted values, important modifier, negative values, and named groups\u002Fpeers. The edge cases that break other parsers.",{"id":123,"title":124,"titles":125,"content":126,"level":36},"\u002Fen\u002Fblog\u002Foxlint-tailwindcss-the-linting-plugin-tailwind-v4-needed#custom-class-detection","Custom class detection",[74,119],"By default the plugin detects classes in common attributes, 14 utility functions, tw tagged templates, and variables named className\u002Fclasses\u002Fstyle. You can extend these defaults via settings.tailwindcss — all values are additive: {\n  \"settings\": {\n    \"tailwindcss\": {\n      \"attributes\": [\"overlayClassName\"],\n      \"callees\": [\"myHelper\"],\n      \"tags\": [\"css\"],\n      \"variablePatterns\": [\"^tw\"],\n    },\n  },\n} This applies to all 22 rules at once. If you need to remove a built-in default, use exclude in the same settings block.",{"id":128,"title":129,"titles":130,"content":131,"level":15},"\u002Fen\u002Fblog\u002Foxlint-tailwindcss-the-linting-plugin-tailwind-v4-needed#the-story-behind-it","The story behind it",[74],"I started by planning what I wanted, the stack I was going to use, and how I wanted everything to work. After planning the implementation with Claude Code, the iteration began until reaching the current 22 rules. The repo includes a CLAUDE.md and configured skills that allow any contributor to use the same workflow to write new rules — the same tool the plugin was built with. If you want to add a rule, Claude Code already knows how to do it in this project. The project runs entirely on the VoidZero tool ecosystem. tsdown for the build, oxfmt for formatting, vitest for testing, tsgo (native TypeScript 7 in Go) for type checking, and of course oxlint for linting the plugin itself. Every tool in the chain is built on Rust or optimized for speed. It wasn't a cosmetic decision, it was deliberate dogfooding. If you're going to make a plugin for oxlint, it makes sense that the entire toolchain is from the same ecosystem. And if you're going to develop with an AI agent, it makes sense that the repo is prepared for it.",{"id":133,"title":134,"titles":135,"content":136,"level":15},"\u002Fen\u002Fblog\u002Foxlint-tailwindcss-the-linting-plugin-tailwind-v4-needed#getting-started","Getting started",[74],"pnpm add -D oxlint-tailwindcss Add the plugin to your .oxlintrc.json: {\n  \"jsPlugins\": [\"oxlint-tailwindcss\"],\n  \"rules\": {\n    \"tailwindcss\u002Fno-unknown-classes\": \"error\",\n    \"tailwindcss\u002Fno-duplicate-classes\": \"error\",\n    \"tailwindcss\u002Fno-conflicting-classes\": \"error\",\n    \"tailwindcss\u002Fno-deprecated-classes\": \"error\",\n    \"tailwindcss\u002Fno-unnecessary-whitespace\": \"error\",\n    \"tailwindcss\u002Fenforce-sort-order\": \"warn\",\n    \"tailwindcss\u002Fenforce-shorthand\": \"warn\",\n    \"tailwindcss\u002Fno-hardcoded-colors\": \"warn\"\n    \u002F\u002F...\n  }\n} Run oxlint. That's it.",{"id":138,"title":139,"titles":140,"content":141,"level":15},"\u002Fen\u002Fblog\u002Foxlint-tailwindcss-the-linting-plugin-tailwind-v4-needed#try-it","Try it",[74],"The plugin is functional, tested, and used in production. But a linter gets better with real feedback from real projects.\nIf you try it and find a case it doesn't handle well, open an issue. If you want to contribute a rule, the repo is already set up so you can iterate with Claude Code from minute one. And if it was simply useful to you, a star on GitHub helps more people find it. GitHub · npm html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .s5Dmg, html code.shiki .s5Dmg{--shiki-default:#FFCB6B}html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .sC9rS, html code.shiki .sC9rS{--shiki-default:#464B5D;--shiki-default-font-style:italic}",{"id":143,"title":144,"titles":145,"content":146,"level":9},"\u002Fen\u002Fblog\u002Fscreaming-architecture-the-key-to-scalable-frontend","Screaming Architecture: the key to a scalable frontend",[],"Your frontend project should scream what it does, not what it is built with. Concrete migration examples, framework coexistence with Nuxt, and shared state handling.",{"id":148,"title":149,"titles":150,"content":151,"level":15},"\u002Fen\u002Fblog\u002Fscreaming-architecture-the-key-to-scalable-frontend#the-pattern-i-saw-everywhere","The pattern I saw everywhere",[144],"Across multiple companies where I worked, I kept running into the same pattern. Teams knew that organizing by features was important, the intention was there. But in practice, the feature ended up scattered across the entire project: components\u002Fauth\u002F, services\u002Fauth\u002F, store\u002Fauth\u002F, views\u002Fauth\u002F. The feature name appeared in every technical folder, but never in a single place. src\u002F\n├── components\u002F\n│   ├── auth\u002F          # LoginButton.vue, RegisterForm.vue\n│   ├── products\u002F      # ProductCard.vue, ProductList.vue\n│   └── orders\u002F        # OrderSummary.vue\n├── views\u002F\n│   ├── auth\u002F          # LoginView.vue, RegisterView.vue\n│   ├── products\u002F      # ProductsView.vue\n│   └── orders\u002F        # OrdersView.vue\n├── services\u002F\n│   ├── auth.service.ts\n│   ├── products.service.ts\n│   └── orders.service.ts\n├── store\u002F\n│   ├── auth.store.ts\n│   ├── products.store.ts\n│   └── orders.store.ts\n└── utils\u002F At first glance it looks organized. But when you need to change something in Auth, you touch 4 folders. When a new dev joins, they have to mentally reconstruct which files belong to which feature. And as the app grows, each technical folder becomes a junk drawer with 30+ files. The problem isn't a lack of organization. It's that the organization screams \"technology\" instead of screaming \"business.\"",{"id":153,"title":154,"titles":155,"content":156,"level":15},"\u002Fen\u002Fblog\u002Fscreaming-architecture-the-key-to-scalable-frontend#the-analogy-that-explains-it-all","The analogy that explains it all",[144],"Imagine a house blueprint. You don't see a \"bricks\" section, a \"cement\" section, and a \"windows\" section. You see \"kitchen,\" \"bedroom,\" \"bathroom.\" The blueprints scream the purpose of each space, not the materials it's built with. Screaming Architecture seeks the same for your code. Your top-level folder structure should scream the features of your application, not the technologies you use.",{"id":158,"title":159,"titles":160,"content":161,"level":15},"\u002Fen\u002Fblog\u002Fscreaming-architecture-the-key-to-scalable-frontend#what-you-gain-with-this-approach","What you gain with this approach",[144],"Instant understanding: you open the project and at a glance you know what the business does.Fast onboarding: new devs understand the project structure in minutes, not days.Localized changes: modifying a feature doesn't force you to touch 4 folders. Everything is in one place.Real scalability: adding a new feature means creating a folder, not inserting files in 6 different places.Easier testing: each module is an isolated unit you can test independently.Safe refactoring: moving or deleting an entire feature means moving or deleting a folder.",{"id":163,"title":164,"titles":165,"content":166,"level":15},"\u002Fen\u002Fblog\u002Fscreaming-architecture-the-key-to-scalable-frontend#applying-screaming-architecture","Applying Screaming Architecture",[144],"The idea is simple: group by features or business domains. Each folder contains everything needed for that functionality. src\u002F\n├── modules\u002F\n│   ├── Auth\u002F\n│   │   ├── components\u002F   # LoginButton.vue, RegisterForm.vue\n│   │   ├── views\u002F        # LoginView.vue, RegisterView.vue\n│   │   ├── routes\u002F       # Routes for this module\n│   │   ├── store\u002F        # auth.store.ts\n│   │   └── services\u002F     # auth.service.ts\n│   ├── Products\u002F\n│   │   ├── components\u002F   # ProductCard.vue, ProductList.vue\n│   │   ├── views\u002F        # ProductsView.vue, ProductDetailView.vue\n│   │   ├── store\u002F        # products.store.ts\n│   │   └── services\u002F     # products.service.ts\n│   └── Orders\u002F\n│       ├── components\u002F\n│       ├── views\u002F\n│       ├── store\u002F\n│       └── services\u002F\n├── shared\u002F\n│   ├── ui\u002F\n│   │   ├── components\u002F   # BaseButton.vue, ModalBase.vue\n│   │   └── composables\u002F  # useModal.ts, useDarkMode.ts\n│   ├── composables\u002F      # useDebounce.ts, useLocalStorage.ts\n│   ├── utils\u002F            # formatDate.ts, validateEmail.ts\n│   └── assets\u002F           # Global images, base styles\n├── layouts\u002F              # DefaultLayout.vue, AuthLayout.vue\n├── router\u002F               # General router\n├── app.vue\n└── main.ts Not every module needs the same internal structure. Auth may have store\u002F and services\u002F, but a Landing module might just be components\u002F and views\u002F. Each module defines what it needs.",{"id":168,"title":169,"titles":170,"content":171,"level":15},"\u002Fen\u002Fblog\u002Fscreaming-architecture-the-key-to-scalable-frontend#from-theory-to-practice-migrating-a-feature","From theory to practice: migrating a feature",[144],"Let's take the Auth example from the disaggregated structure and see what the migration looks like: Before (4 folders): src\u002Fcomponents\u002Fauth\u002FLoginButton.vue\nsrc\u002Fcomponents\u002Fauth\u002FRegisterForm.vue\nsrc\u002Fviews\u002Fauth\u002FLoginView.vue\nsrc\u002Fviews\u002Fauth\u002FRegisterView.vue\nsrc\u002Fservices\u002Fauth.service.ts\nsrc\u002Fstore\u002Fauth.store.ts After (1 folder): src\u002Fmodules\u002FAuth\u002Fcomponents\u002FLoginButton.vue\nsrc\u002Fmodules\u002FAuth\u002Fcomponents\u002FRegisterForm.vue\nsrc\u002Fmodules\u002FAuth\u002Fviews\u002FLoginView.vue\nsrc\u002Fmodules\u002FAuth\u002Fviews\u002FRegisterView.vue\nsrc\u002Fmodules\u002FAuth\u002Fservices\u002Fauth.service.ts\nsrc\u002Fmodules\u002FAuth\u002Fstore\u002Fauth.store.ts The imports change, the code doesn't. If your Auth module needs to expose something to the rest of the app (a guard, a composable, the user state), you can create an index.ts that serves as the module's public API: \u002F\u002F src\u002Fmodules\u002FAuth\u002Findex.ts\nexport { useAuthStore } from '.\u002Fstore\u002Fauth.store'\nexport { useCurrentUser } from '.\u002Fcomposables\u002FuseCurrentUser'\nexport { authGuard } from '.\u002Froutes\u002Fguards' The rest of the app imports from @\u002Fmodules\u002FAuth, not from internal folders. If you refactor the module's internal structure, external imports don't break.",{"id":173,"title":174,"titles":175,"content":176,"level":15},"\u002Fen\u002Fblog\u002Fscreaming-architecture-the-key-to-scalable-frontend#framework-coexistence","Framework coexistence",[144],"If you use Nuxt, Next, or any framework with folder conventions (pages\u002F, composables\u002F, components\u002F), the obvious question is: how does this coexist with Screaming Architecture? Framework conventions handle routing and auto-imports. Screaming Architecture handles business logic. In Nuxt, for example, pages\u002F defines routes, but the page can be a thin wrapper that imports the actual module: \u003C!-- pages\u002Fauth\u002Flogin.vue -->\n\u003Ctemplate>\n  \u003CLoginView \u002F>\n\u003C\u002Ftemplate>\n\n\u003Cscript setup lang=\"ts\">\nimport LoginView from '~\u002Fmodules\u002FAuth\u002Fviews\u002FLoginView.vue'\n\u003C\u002Fscript> Nuxt handles routing (\u002Fauth\u002Flogin), and your Auth module handles the logic. pages\u002F stays thin, just an entry point. The same applies to composables\u002F. Global composables (auto-imported by Nuxt) go in the framework's conventional folder. Feature-specific composables go inside the module: composables\u002F              # Auto-imported by Nuxt (global)\n├── useTheme.ts\n└── useBreakpoint.ts\nmodules\u002FAuth\u002Fcomposables\u002F # Auth-specific (manual import)\n├── useCurrentUser.ts\n└── useAuthRedirect.ts",{"id":178,"title":179,"titles":180,"content":181,"level":15},"\u002Fen\u002Fblog\u002Fscreaming-architecture-the-key-to-scalable-frontend#cross-cutting-concerns-what-goes-in-shared","Cross-cutting concerns: what goes in shared",[144],"The most common question when applying this: what happens when two modules need the same thing? The rule is simple. If something is specific to a feature, it goes in its module. If two or more features use it, it goes in shared\u002F. shared\u002Fui\u002F is for generic UI components with no business logic: buttons, modals, inputs. They're the building blocks, not the features. shared\u002Fcomposables\u002F is for reusable logic without business state: useDebounce, useLocalStorage, useIntersectionObserver. shared\u002Futils\u002F is for pure functions: formatDate, slugify, validateEmail. What about shared state? If Auth and Orders both need user data, Auth owns the state. Orders imports useCurrentUser from @\u002Fmodules\u002FAuth. If over time more modules need the same data, you can move that composable to shared\u002F. But don't do it preemptively. Start with the owning module and only move when there's real evidence it's cross-cutting.",{"id":183,"title":184,"titles":185,"content":186,"level":15},"\u002Fen\u002Fblog\u002Fscreaming-architecture-the-key-to-scalable-frontend#when-to-use-it-and-when-not-to","When to use it and when not to",[144],"This approach works best for medium to large projects with real business logic and multi-dev teams. If your app has 5+ features that grow independently, Screaming Architecture will bring order to your life. For MVPs, landing pages, or simple CRUDs, it might be overkill. You don't need a module structure for 3 views and a form. If your project has the potential to grow, adopting it early saves you the painful migration later. And if you already have a large project with a generic structure, you can migrate progressively, one module at a time, starting with the feature that grows the most. Next time you start a project, think about what the structure will look like when the team grows. If your folders only say components\u002F and utils\u002F, you know where to start. html pre.shiki code .sC9rS, html code.shiki .sC9rS{--shiki-default:#464B5D;--shiki-default-font-style:italic}html pre.shiki code .s6cf3, html code.shiki .s6cf3{--shiki-default:#89DDFF;--shiki-default-font-style:italic}html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}",{"id":188,"title":189,"titles":190,"content":191,"level":9},"\u002Fen\u002Fblog\u002Fwhat-i-learned-building-design-systems-and-what-i-would-do-differently","What I learned building Design Systems and what I would do differently",[],"Real lessons from building Design Systems in Vue and TypeScript: common mistakes, architecture decisions, and what I would do differently today.",{"id":193,"title":194,"titles":195,"content":196,"level":15},"\u002Fen\u002Fblog\u002Fwhat-i-learned-building-design-systems-and-what-i-would-do-differently#what-is-a-design-system","What is a Design System?",[189],"Simply put, it's a collection of rules, components, and guidelines that act as a single source of truth for designing and building interfaces. A good Design System defines everything from colors, typography, and spacing, to how buttons, forms, alerts, and more should behave. But it goes far beyond the visual: it also defines how design and development communicate, how patterns are documented, and how the product evolves consistently. The goal is for the entire team to speak the same language, design faster, and build products that feel coherent—without reinventing the wheel on every screen. It’s not just a component library. It’s a way of working that seeks to align design, code, and user experience. I'll go into more detail about how I built mine and what I learned, but first I want to tell you what it was like to face this idea from scratch.",{"id":198,"title":199,"titles":200,"content":201,"level":15},"\u002Fen\u002Fblog\u002Fwhat-i-learned-building-design-systems-and-what-i-would-do-differently#how-i-ended-up-building-design-systems","How I Ended Up Building Design Systems",[189],"After working on different projects and teams, I began to notice patterns that kept repeating: The same button designed three different waysInconsistent behavior in similar componentsDuplicated or hard-to-maintain codeMisunderstandings between design and developmentInterfaces that looked great in Figma, but not in production Every time a product grew, so did the visual and technical chaos. And with it, the time we lost solving the same problems over and over again. That’s when I realized we needed more than just a component library. We needed a shared way to build interfaces, with clear rules, centralized decisions, and a source of truth for all teams.",{"id":203,"title":204,"titles":205,"content":206,"level":15},"\u002Fen\u002Fblog\u002Fwhat-i-learned-building-design-systems-and-what-i-would-do-differently#expectations-vs-reality","Expectations vs. Reality",[189],"Remember when you had to do group projects at school?\nEveryone did their part separately, put it all together at the end… and the result was a total \"Frankenstein.\" Well, building a Design System often feels like that. At first, you think it’s going to organize everything, magically uniting design and development. But in practice, misunderstandings, shortcuts, and crossed decisions appear, and what should be a coherent system starts to fall apart. Here are some of the biggest differences I experienced between what I imagined… and what actually happened:",{"id":208,"title":209,"titles":210,"content":211,"level":36},"\u002Fen\u002Fblog\u002Fwhat-i-learned-building-design-systems-and-what-i-would-do-differently#expectation-1","Expectation 1:",[189,204],"“I’ll create a reusable component library and the team will happily use it.” Reality:\nSome devs ignore it, others break it, and others don’t know how to use it.\nWithout documentation, onboarding, and internal support, no one adopts it as you expected.",{"id":213,"title":214,"titles":215,"content":216,"level":36},"\u002Fen\u002Fblog\u002Fwhat-i-learned-building-design-systems-and-what-i-would-do-differently#expectation-2","Expectation 2:",[189,204],"“The design is in Figma, I just have to replicate it.” Reality:\nThe design doesn’t consider states, errors, focus, interaction, loading...\nI ended up making a lot of technical decisions that weren’t defined.",{"id":218,"title":219,"titles":220,"content":221,"level":36},"\u002Fen\u002Fblog\u002Fwhat-i-learned-building-design-systems-and-what-i-would-do-differently#expectation-3","Expectation 3:",[189,204],"“Once the components are done, we won’t need to touch them again.” Reality:\nDesign evolves, new requirements come up, and each change affects multiple parts of the system.\nA Design System needs constant maintenance.",{"id":223,"title":224,"titles":225,"content":226,"level":36},"\u002Fen\u002Fblog\u002Fwhat-i-learned-building-design-systems-and-what-i-would-do-differently#expectation-4","Expectation 4:",[189,204],"“I’ll create generic and reusable components for everything.” Reality:\nOverly flexible components end up being hard to maintain, test, or understand.\nSometimes it’s better to have clear variants than a single component with 15 props.",{"id":228,"title":229,"titles":230,"content":231,"level":36},"\u002Fen\u002Fblog\u002Fwhat-i-learned-building-design-systems-and-what-i-would-do-differently#expectation-5","Expectation 5:",[189,204],"“Design and development will work as one team.” Reality:\nWithout clear processes and a common language, misunderstandings happen all the time.\nCollaboration isn’t automatic. You have to build those bridges intentionally.",{"id":233,"title":234,"titles":235,"content":236,"level":36},"\u002Fen\u002Fblog\u002Fwhat-i-learned-building-design-systems-and-what-i-would-do-differently#expectation-6","Expectation 6:",[189,204],"“With Tailwind, I can do everything without a Design System.” Reality:\nTailwind helps, but without shared design decisions, tokens, and a clear structure, your project still becomes inconsistent.",{"id":238,"title":239,"titles":240,"content":241,"level":36},"\u002Fen\u002Fblog\u002Fwhat-i-learned-building-design-systems-and-what-i-would-do-differently#expectation-7","Expectation 7:",[189,204],"“I’ll have the system ready in a few weeks.” Reality:\nA Design System is never finished. It’s a living system that grows, adapts, and needs to evolve alongside your product.",{"id":243,"title":244,"titles":245,"content":246,"level":15},"\u002Fen\u002Fblog\u002Fwhat-i-learned-building-design-systems-and-what-i-would-do-differently#design-and-development-needs","Design and Development Needs",[189],"When you set out to build a Design System (whether from scratch or based on an existing foundation) there are always two sides of the same coin: what Design and Development want and need. That’s where all the problems challenges begin. Let’s take a look at what each team needs.",{"id":248,"title":249,"titles":250,"content":251,"level":36},"\u002Fen\u002Fblog\u002Fwhat-i-learned-building-design-systems-and-what-i-would-do-differently#design","Design",[189,244],"Visual consistency: A unified visual language (colors, typography, spacing, iconography, etc.).Speed and efficiency: The ability to design interfaces without reinventing the same components every time.Scalable designs: What works on one screen should work on others (web, mobile, responsive...).Clear, reusable design tokens: Technical definitions for colors, sizes, fonts, z-index, etc.Direct alignment with implementation: What’s designed should look the same in production.",{"id":253,"title":254,"titles":255,"content":256,"level":36},"\u002Fen\u002Fblog\u002Fwhat-i-learned-building-design-systems-and-what-i-would-do-differently#development","Development",[189,244],"True component reuse: A stable, well-documented, and easy-to-use library.Functional and technical consistency: Same prop patterns, slots, states, accessibility, validation, etc.Ease of maintenance: Clean code, clear naming, tests, and no duplicated logic.Living documentation: Storybook (or similar), with real examples—not just theory.Adaptability across contexts: A button (for example) should work the same in all our applications. Each team has its own priorities, but for a Design System to work, they must collaborate, making decisions based on shared definitions. That’s where the real intersection happens.",{"id":258,"title":259,"titles":260,"content":261,"level":36},"\u002Fen\u002Fblog\u002Fwhat-i-learned-building-design-systems-and-what-i-would-do-differently#shared-needs-the-balance-point","Shared Needs: The Balance Point",[189,244],"Common language: Tokens, component names, variants, states. Everyone should speak the same language.Smooth design–development process: Clear handoffs from Figma (or whatever tool is used), efficient handovers, mutual feedback.Versioning and change control: So that design changes can be implemented and communicated without breaking the system.Cross-team adoption: It shouldn’t be stuck in one team. It has to be easy to understand and use by everyone. In short, a Design System needs to be strict enough to uphold design definitions and prevent undocumented visual changes, but flexible enough to extend and adapt without breaking things.",{"id":263,"title":264,"titles":265,"content":266,"level":15},"\u002Fen\u002Fblog\u002Fwhat-i-learned-building-design-systems-and-what-i-would-do-differently#how-to-drive-system-adoption","How to Drive System Adoption",[189],"Having a technically perfect Design System doesn’t mean much if no one uses it. One of the biggest challenges isn’t building the system, it’s getting the team to actually adopt it. And I don’t just mean “being aware it exists,” I mean integrating it into their daily workflow, respecting it, questioning it, and keeping it alive. Here’s what helped me (and what I learned the hard way): Involve people from the start: If the system is built in isolation, it will generate resistance. The more voices involved early on, the more ownership it creates.Show value quickly: A useful, well-documented, easy-to-use component is worth more than a promise of “someday we’ll have everything tidy.”Document with empathy: It’s not just about how to use a component, it’s about how someone new will understand it. Real examples, screenshots, use cases, FAQs—all of it helps.Provide internal support: If someone has a problem with a component, there should be a quick way to solve it. Slack, issues, pair programming… whatever it takes, just don’t let people feel alone—and have a clear way to report issues.Celebrate usage: When someone adopts a component, improves it, or reports a bug—celebrate it. Small recognitions help the system feel alive, collaborative, and valuable. Adoption isn’t an event, it’s a process. It takes time, patience, and lots of communication.",{"id":268,"title":269,"titles":270,"content":271,"level":15},"\u002Fen\u002Fblog\u002Fwhat-i-learned-building-design-systems-and-what-i-would-do-differently#lessons-learned-and-final-recommendations","Lessons Learned and Final Recommendations",[189],"After all this, here are some takeaways I wish I had known earlier: A Design System isn’t a project, it’s a product. And like any product, it needs research, design, maintenance, communication, and evolution.Start with what hurts the most: Don’t try to solve everything from day one. Identify your biggest pain points and start there.Perfection isn’t the goal: There will always be debt, components to improve, and decisions to revisit. The goal is usefulness and evolution.No adoption, no system: If the team doesn’t use it, it’s just another folder in your repo.Every team is different: Don’t copy someone else’s system expecting it to work the same. Get inspired, sure—but adapt it to your context.Anticipate problems: Your Design System will break. Definitions will change. Plan for that. Build things modular and flexible.Use Atomic Design: But use it wisely. Every component is a world of its own. The more atomic each one is, the easier it is to update or extend without breaking everything. A Design System is a long-term investment. It can be challenging, even frustrating at times, but once it starts working, the whole team benefits from speed, consistency, and confidence.",{"post":273,"surround":867,"translatedPost":870},{"id":274,"title":6,"author":275,"body":276,"date":852,"dateModified":852,"description":8,"extension":853,"img":854,"meta":855,"navigation":856,"path":5,"published":856,"readingTime":857,"seo":858,"sitemap":859,"slug":854,"stem":860,"tags":861,"union":865,"__hash__":866},"blog_en\u002Fen\u002Fblog\u002Fhow-to-migrate-with-claude-code-and-not-die-trying.md","Sergio Azócar",{"type":277,"value":278,"toc":837},"minimark",[279,283,287,290,293,296,299,307,310,313,316,344,347,350,353,356,369,372,376,379,410,413,467,470,473,476,522,525,584,587,590,654,657,663,666,692,695,702,705,710,713,727,738,741,749,752,755,796,799,825,828,831,834],[280,281,12],"h2",{"id":282},"migrating-no-longer-hurts",[284,285,286],"p",{},"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.",[284,288,289],{},"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.",[284,291,292],{},"Not anymore.",[280,294,18],{"id":295},"the-historical-pain-of-migrating",[284,297,298],{},"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.",[284,300,301,302,306],{},"Migrating a design system was similar but worse. You change the token system, the base components, the prop names. Suddenly every ",[303,304,305],"code",{},"\u003CButton variant=\"primary\" \u002F>"," in the app needs review, and when you have hundreds of those, no team signs off on a full quarter just for that.",[284,308,309],{},"The pattern was always the same, high load, low motivation, indefinite procrastination.",[280,311,23],{"id":312},"why-migrations-are-the-perfect-scenario-for-an-agent",[284,314,315],{},"After doing several with Claude Code, I've identified three conditions where they're ideal:",[317,318,319,327,333],"ol",{},[320,321,322,326],"li",{},[323,324,325],"strong",{},"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.",[320,328,329,332],{},[323,330,331],{},"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.",[320,334,335,338,339,343],{},[323,336,337],{},"Accessible documentation."," The target tool almost always has a ",[340,341,342],"em",{},"migration guide",". Pass the guide to the agent, give it the project context, and it stops making things up.",[284,345,346],{},"If your task meets all three, stop procrastinating. It's work for an agent.",[280,348,28],{"id":349},"case-1-a-monorepos-toolchain",[284,351,352],{},"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.",[284,354,355],{},"Two bottlenecks:",[357,358,359,366],"ul",{},[320,360,361,362,365],{},"Biome and tsc required ",[303,363,364],{},"NODE_OPTIONS='--max-old-space-size=8192'"," to avoid out of memory crashes during CI.",[320,367,368],{},"tsc, single-threaded, took 25 seconds to typecheck the entire monorepo.",[284,370,371],{},"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.",[373,374,33],"h3",{"id":375},"migration-to-oxlint-oxfmt",[284,377,378],{},"The steps:",[317,380,381,384,391,398,404,407],{},[320,382,383],{},"Replace Biome with oxlint and oxfmt.",[320,385,386,387,390],{},"Configure ",[303,388,389],{},".oxlintrc.json"," with 129 active rules.",[320,392,393,394,397],{},"Configure an equivalent ",[303,395,396],{},".oxfmtrc.json",".",[320,399,400,401,397],{},"Update scripts in ",[303,402,403],{},"package.json",[320,405,406],{},"Adjust the GitHub Actions pipeline.",[320,408,409],{},"Adjust the editor config.",[284,411,412],{},"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.",[414,415,416,435],"table",{},[417,418,419],"thead",{},[420,421,422,426,429,432],"tr",{},[423,424,425],"th",{},"Metric",[423,427,428],{},"Biome",[423,430,431],{},"oxlint (Jan 2026)",[423,433,434],{},"oxlint (Mar 2026)",[436,437,438,453],"tbody",{},[420,439,440,444,447,450],{},[441,442,443],"td",{},"Average time",[441,445,446],{},"3.70s",[441,448,449],{},"1.04s",[441,451,452],{},"0.49s",[420,454,455,458,461,464],{},[441,456,457],{},"Files analyzed",[441,459,460],{},"1,227",[441,462,463],{},"1,262",[441,465,466],{},"~1,670",[284,468,469],{},"oxlint was processing more files with more rules in a fraction of the time. And the tool keeps improving with every release.",[373,471,39],{"id":472},"migration-to-tsgo",[284,474,475],{},"TypeScript 7 (tsgo) is the rewrite of tsc (TypeScript Compiler) in Go. Microsoft promised 7x to 10x improvements. The migration was even more straightforward:",[317,477,478,484,494,509,519],{},[320,479,480,481,397],{},"Install ",[303,482,483],{},"@typescript\u002Fnative-preview",[320,485,486,487,490,491,397],{},"Change the script from ",[303,488,489],{},"NODE_OPTIONS='--max-old-space-size=8192' tsc"," to ",[303,492,493],{},"tsgo",[320,495,496,497,500,501,504,505,508],{},"Adjust ",[303,498,499],{},"tsconfig.json"," (drop ",[303,502,503],{},"baseUrl",", add ",[303,506,507],{},".\u002F"," as prefix in paths).",[320,510,511,512,490,515,518],{},"Update the CI cache (from ",[303,513,514],{},".tsbuildinfo",[303,516,517],{},".tsgo-cache",").",[320,520,521],{},"Fix types in places where tsgo is stricter than tsc.",[284,523,524],{},"The bonus, you no longer need the memory config. tsgo runs in Go without Node's constraints.",[414,526,527,542],{},[417,528,529],{},[420,530,531,533,536,539],{},[423,532,425],{},[423,534,535],{},"tsc",[423,537,538],{},"tsgo (Jan 2026)",[423,540,541],{},"tsgo (Mar 2026)",[436,543,544,558,571],{},[420,545,546,549,552,555],{},[441,547,548],{},"Time (cold)",[441,550,551],{},"25.34s",[441,553,554],{},"5.32s",[441,556,557],{},"0.76s",[420,559,560,563,566,569],{},[441,561,562],{},"CPU usage",[441,564,565],{},"~132%",[441,567,568],{},"~430%",[441,570,568],{},[420,572,573,576,579,582],{},[441,574,575],{},"Requires memory config",[441,577,578],{},"Yes (8 GB)",[441,580,581],{},"No",[441,583,581],{},[284,585,586],{},"33 times faster on typecheck, without touching a single line of TypeScript code in the project.",[373,588,44],{"id":589},"the-final-number",[414,591,592,608],{},[417,593,594],{},[420,595,596,599,602,605],{},[423,597,598],{},"Phase",[423,600,601],{},"Before",[423,603,604],{},"Migration",[423,606,607],{},"Today (Apr 2026)",[436,609,610,621,632],{},[420,611,612,615,617,619],{},[441,613,614],{},"Lint",[441,616,446],{},[441,618,449],{},[441,620,452],{},[420,622,623,626,628,630],{},[441,624,625],{},"Typecheck",[441,627,551],{},[441,629,554],{},[441,631,557],{},[420,633,634,639,644,649],{},[441,635,636],{},[323,637,638],{},"Total",[441,640,641],{},[323,642,643],{},"29.04s",[441,645,646],{},[323,647,648],{},"6.36s",[441,650,651],{},[323,652,653],{},"1.25s",[284,655,656],{},"From 29 seconds to 1.25. 23 times faster. And what finally convinced the team were precisely those numbers. Without measuring before and after, the migration is just \"Sergio changed the tools\". With the numbers, it's \"code quality feedback is 23X faster\".",[284,658,659,662],{},[323,660,661],{},"Lesson:"," always measure. That's what turns a migration into a story the team understands and signs off on.",[280,664,49],{"id":665},"case-2-screaming-architecture-in-a-large-frontend",[284,667,668,669,673,674,677,678,677,681,684,685,677,688,691],{},"Before the toolchain migration, I'd done another one: applying ",[670,671,672],"a",{"href":143},"screaming architecture"," to the main product's frontend. Reorganizing the repo from technical folders (",[303,675,676],{},"components\u002F",", ",[303,679,680],{},"services\u002F",[303,682,683],{},"store\u002F",") into feature modules (",[303,686,687],{},"modules\u002FAuth\u002F",[303,689,690],{},"modules\u002FOrders\u002F",", etc).",[284,693,694],{},"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\".",[284,696,697,698,701],{},"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 ",[303,699,700],{},"modules\u002FX\u002F","\". The agent gets the pattern from the first example and applies it everywhere else.",[284,703,704],{},"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.",[284,706,707,709],{},[323,708,661],{}," structural refactors are ideal when the pattern is clear but the volume is high. Define the rules once, let the agent apply them everywhere.",[280,711,54],{"id":712},"case-3-the-redesign-of-this-site",[284,714,715,716,719,720,723,724,397],{},"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 (",[303,717,718],{},"beacon"," and ",[303,721,722],{},"orbit","), dark-only theme, translucent borders with ",[303,725,726],{},"color-mix",[284,728,729,730,733,734,737],{},"The difference with the previous cases is that the project context was entirely mine. I have a well-kept ",[303,731,732],{},"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 ",[303,735,736],{},"border-(muted)"," is or where it's defined. It already knows.",[284,739,740],{},"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.",[284,742,743,745,746,748],{},[323,744,661],{}," investing in a good ",[303,747,732],{}," isn't overhead, it's the difference between a useful agent and one that hallucinates.",[280,750,59],{"id":751},"the-playbook",[284,753,754],{},"After several migrations, this is what I always do:",[317,756,757,763,769,775,784,790],{},[320,758,759,762],{},[323,760,761],{},"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.",[320,764,765,768],{},[323,766,767],{},"Atomic PRs per phase."," In the toolchain case, oxlint and tsgo were separate PRs. If something breaks, you know exactly which phase caused it.",[320,770,771,774],{},[323,772,773],{},"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.",[320,776,777,783],{},[323,778,779,780,782],{},"Keep a ",[303,781,732],{}," up to date."," Stack, conventions, commands, project gotchas. The difference between an agent that understands the repo and one that makes things up.",[320,785,786,789],{},[323,787,788],{},"Measure before and after."," Without numbers there's no story to tell, nor argument to pitch to the team.",[320,791,792,795],{},[323,793,794],{},"Pair programming, not autopilot."," The agent doesn't replace judgment, it amplifies it. You decide what gets done, it runs and brings the feedback.",[280,797,64],{"id":798},"what-not-to-do",[357,800,801,807,813,819],{},[320,802,803,806],{},[323,804,805],{},"Don't ask for everything at once."," \"Migrate the whole repo\" is the worst possible instruction. Split into phases, one at a time.",[320,808,809,812],{},[323,810,811],{},"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.",[320,814,815,818],{},[323,816,817],{},"Don't let it invent paths or APIs."," If it cites a function you don't recognize, verify it exists before accepting the change.",[320,820,821,824],{},[323,822,823],{},"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.",[280,826,69],{"id":827},"closing",[284,829,830],{},"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.",[284,832,833],{},"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.",[284,835,836],{},"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.",{"title":96,"searchDepth":15,"depth":36,"links":838},[839,840,841,842,847,848,849,850,851],{"id":282,"depth":15,"text":12},{"id":295,"depth":15,"text":18},{"id":312,"depth":15,"text":23},{"id":349,"depth":15,"text":28,"children":843},[844,845,846],{"id":375,"depth":36,"text":33},{"id":472,"depth":36,"text":39},{"id":589,"depth":36,"text":44},{"id":665,"depth":15,"text":49},{"id":712,"depth":15,"text":54},{"id":751,"depth":15,"text":59},{"id":798,"depth":15,"text":64},{"id":827,"depth":15,"text":69},"2026-04-28","md",null,{},true,8,{"title":6,"description":8},{"loc":5},"en\u002Fblog\u002Fhow-to-migrate-with-claude-code-and-not-die-trying",[862,863,864],"claude-code","migrations","tooling","how-to-migrate-with-claude-code-and-not-die-trying","BW7GisUgzYUjZKxKDnmPzTknqsB9__xrskze_T9Alrw",[854,868],{"title":74,"path":73,"stem":869,"description":76,"children":-1},"en\u002Fblog\u002Foxlint-tailwindcss-the-linting-plugin-tailwind-v4-needed",{"id":871,"title":872,"author":275,"body":873,"date":852,"dateModified":852,"description":1398,"extension":853,"img":854,"meta":1399,"navigation":856,"path":1400,"published":856,"readingTime":857,"seo":1401,"sitemap":1402,"slug":854,"stem":1403,"tags":1404,"union":865,"__hash__":1405},"blog_es\u002Fes\u002Fblog\u002Fcomo-hacer-migraciones-con-claude-code-y-no-morir-en-el-intento.md","Cómo hacer migraciones con Claude Code y no morir en el intento",{"type":277,"value":874,"toc":1383},[875,879,882,885,888,892,895,901,904,908,911,934,937,941,944,947,958,961,965,968,995,998,1042,1045,1049,1052,1089,1092,1144,1147,1151,1209,1212,1218,1222,1240,1243,1249,1252,1257,1261,1272,1281,1284,1292,1296,1299,1340,1344,1370,1374,1377,1380],[280,876,878],{"id":877},"migrar-dejó-de-doler","Migrar dejó de doler",[284,880,881],{},"Migré el diseño completo de este sitio en un par de días. Antes de Claude Code, esa misma migración me la habría comido en dos semanas, y probablemente la habría postergado seis meses por pura flojera.",[284,883,884],{},"Llevo años haciendo migraciones. Vue 2 a Vue 3, design systems completos, refactors arquitectónicos que tocaban cientos de archivos. Todas tenían algo en común, dolían. Eran proyectos largos, mecánicos, con riesgo alto y poca recompensa visible. La clase de tarea que sabes que hay que hacer pero que sigues postergando porque suena a infierno.",[284,886,887],{},"Ya no.",[280,889,891],{"id":890},"el-dolor-histórico-de-migrar","El dolor histórico de migrar",[284,893,894],{},"Migrar de Vue 2 a Vue 3 era un proyecto trimestral. No por las dificultades técnicas reales (la mayoría era reemplazar APIs deprecadas), sino por el volumen. Cientos de componentes, cada uno con sus particularidades, todos había que tocarlos. Sumarle Pinia, Vue Router 4, los plugins de terceros que no soportaban v3 todavía. Y mientras hacías la migración, el resto del equipo seguía mergeando features en la rama principal. Cuando intentabas mergear de vuelta, era un infierno.",[284,896,897,898,900],{},"Migrar un design system era parecido pero peor. Cambias el sistema de tokens, los componentes base, los nombres de las props. De pronto cada ",[303,899,305],{}," que existía en la app necesita revisión, y cuando son cientos de ocurrencias, ningún equipo te firma un Q completo solo para eso.",[284,902,903],{},"El patrón siempre era el mismo, alta carga, baja motivación, postergación indefinida.",[280,905,907],{"id":906},"por-qué-las-migraciones-son-el-escenario-perfecto-para-un-agente","Por qué las migraciones son el escenario perfecto para un agente",[284,909,910],{},"Después de hacer varias con Claude Code, identifiqué tres condiciones dónde son ideales:",[317,912,913,919,925],{},[320,914,915,918],{},[323,916,917],{},"Alto volumen de cambios mecánicos."," El PR de oxlint en mi trabajo actual tocó más de 700 archivos. La mayoría eran ajustes triviales (orden de imports, patrones equivalentes). Es exactamente el tipo de trabajo que es humanamente tedioso y que un agente lo hace en minutos.",[320,920,921,924],{},[323,922,923],{},"Feedback claro y automatizable."," Linter, Typecheck, Tests, pasan o no pasan. El Build compila o no compila. Cada paso es un loop con respuesta binaria, justo el tipo de señal que un agente necesita para iterar sin supervisión continua.",[320,926,927,930,931,933],{},[323,928,929],{},"Documentación accesible."," La herramienta de destino casi siempre tiene un ",[340,932,342],{},". Le pasas el guide al agente, le das el contexto del proyecto, y deja de inventar.",[284,935,936],{},"Si tu tarea cumple las tres, deja de procrastinar. Es trabajo para un agente.",[280,938,940],{"id":939},"caso-1-el-toolchain-de-un-monorepo","Caso 1: el toolchain de un monorepo",[284,942,943],{},"El equipo en mi trabajo actual operaba un monorepo con 7 aplicaciones en Cloudflare Workers, alrededor de 1.670 archivos de TypeScript gestionados con pnpm workspaces. El pipeline de calidad usaba Biome para lint y format, y tsc para type-check.",[284,945,946],{},"Dos cuellos de botella:",[357,948,949,955],{},[320,950,951,952,954],{},"Biome y tsc requerían ",[303,953,364],{}," para no caer por out of memory durante CI.",[320,956,957],{},"tsc, single-threaded, se demoraba 25 segundos en chequear los tipos del monorepo completo.",[284,959,960],{},"Llevábamos meses mirando lo que el equipo de oxc (oxlint, oxfmt, todo lo que están construyendo en Rust) iba sacando. La pregunta no era si valía la pena migrar, sino cuánto esfuerzo iba a costar.",[373,962,964],{"id":963},"migración-a-oxlint-oxfmt","Migración a oxlint + oxfmt",[284,966,967],{},"Los pasos:",[317,969,970,973,979,984,989,992],{},[320,971,972],{},"Reemplazar Biome por oxlint y oxfmt.",[320,974,975,976,978],{},"Configurar ",[303,977,389],{}," con 129 reglas activas.",[320,980,975,981,983],{},[303,982,396],{}," equivalente.",[320,985,986,987,397],{},"Actualizar scripts en ",[303,988,403],{},[320,990,991],{},"Ajustar el pipeline en GitHub Actions.",[320,993,994],{},"Ajustar la config del editor.",[284,996,997],{},"El bulk del trabajo no fue cambiar las herramientas. Fue ajustar el código para cumplir las nuevas reglas (la mayoría sobre orden de imports y patrones equivalentes). Claude Code hacía los cambios, corríamos las validaciones, ajustábamos, repetíamos.",[414,999,1000,1015],{},[417,1001,1002],{},[420,1003,1004,1007,1009,1012],{},[423,1005,1006],{},"Métrica",[423,1008,428],{},[423,1010,1011],{},"oxlint (ene 2026)",[423,1013,1014],{},"oxlint (mar 2026)",[436,1016,1017,1028],{},[420,1018,1019,1022,1024,1026],{},[441,1020,1021],{},"Tiempo promedio",[441,1023,446],{},[441,1025,449],{},[441,1027,452],{},[420,1029,1030,1033,1036,1039],{},[441,1031,1032],{},"Archivos analizados",[441,1034,1035],{},"1.227",[441,1037,1038],{},"1.262",[441,1040,1041],{},"~1.670",[284,1043,1044],{},"oxlint procesaba más archivos con más reglas en una fracción del tiempo. Y la herramienta sigue mejorando con cada release.",[373,1046,1048],{"id":1047},"migración-a-tsgo","Migración a tsgo",[284,1050,1051],{},"TypeScript 7 (tsgo) es la reescritura de tsc (TypeScript Compiler) en Go. Microsoft prometía mejoras de 7x a 10x. La migración fue todavía más directa:",[317,1053,1054,1059,1067,1079,1086],{},[320,1055,1056,1057,397],{},"Instalar ",[303,1058,483],{},[320,1060,1061,1062,1064,1065,397],{},"Cambiar el script de ",[303,1063,489],{}," a ",[303,1066,493],{},[320,1068,1069,1070,1072,1073,1075,1076,1078],{},"Ajustar ",[303,1071,499],{}," (sacar ",[303,1074,503],{},", agregar ",[303,1077,507],{}," como prefijo en paths).",[320,1080,1081,1082,1064,1084,518],{},"Actualizar la caché en CI (de ",[303,1083,514],{},[303,1085,517],{},[320,1087,1088],{},"Corregir tipos en los lugares donde tsgo es más estricto que tsc.",[284,1090,1091],{},"El bonus, ya no necesitas la config de memoria. tsgo corre en Go sin las restricciones de Node.",[414,1093,1094,1108],{},[417,1095,1096],{},[420,1097,1098,1100,1102,1105],{},[423,1099,1006],{},[423,1101,535],{},[423,1103,1104],{},"tsgo (ene 2026)",[423,1106,1107],{},"tsgo (mar 2026)",[436,1109,1110,1121,1132],{},[420,1111,1112,1115,1117,1119],{},[441,1113,1114],{},"Tiempo (cold)",[441,1116,551],{},[441,1118,554],{},[441,1120,557],{},[420,1122,1123,1126,1128,1130],{},[441,1124,1125],{},"Uso CPU",[441,1127,565],{},[441,1129,568],{},[441,1131,568],{},[420,1133,1134,1137,1140,1142],{},[441,1135,1136],{},"Requiere config de memoria",[441,1138,1139],{},"Sí (8 GB)",[441,1141,581],{},[441,1143,581],{},[284,1145,1146],{},"33 veces más rápido en typecheck, sin tocar una sola línea de código TypeScript del proyecto.",[373,1148,1150],{"id":1149},"el-número-final","El número final",[414,1152,1153,1169],{},[417,1154,1155],{},[420,1156,1157,1160,1163,1166],{},[423,1158,1159],{},"Fase",[423,1161,1162],{},"Antes",[423,1164,1165],{},"Migración",[423,1167,1168],{},"Hoy (abril 2026)",[436,1170,1171,1181,1191],{},[420,1172,1173,1175,1177,1179],{},[441,1174,614],{},[441,1176,446],{},[441,1178,449],{},[441,1180,452],{},[420,1182,1183,1185,1187,1189],{},[441,1184,625],{},[441,1186,551],{},[441,1188,554],{},[441,1190,557],{},[420,1192,1193,1197,1201,1205],{},[441,1194,1195],{},[323,1196,638],{},[441,1198,1199],{},[323,1200,643],{},[441,1202,1203],{},[323,1204,648],{},[441,1206,1207],{},[323,1208,653],{},[284,1210,1211],{},"De 29 segundos a 1.25. 23 veces más rápido. Y lo que terminó de convencer al equipo fueron precisamente esos números. Sin medir antes y después, la migración es solo \"Sergio cambió las herramientas\". Con los números, es \"el feedback de calidad de código es 23X más rápido\".",[284,1213,1214,1217],{},[323,1215,1216],{},"Lección:"," mide siempre. Es lo que convierte una migración en una historia que el equipo entiende y firma.",[280,1219,1221],{"id":1220},"caso-2-screaming-architecture-en-un-frontend-grande","Caso 2: screaming architecture en un frontend grande",[284,1223,1224,1225,1228,1229,677,1231,677,1233,1235,1236,677,1238,691],{},"Antes de la migración del toolchain, había hecho otra: aplicar ",[670,1226,672],{"href":1227},"\u002Fes\u002Fblog\u002Fscreaming-architecture-la-clave-para-un-frontend-escalable"," al frontend del producto principal. Reorganizar el repo desde carpetas técnicas (",[303,1230,676],{},[303,1232,680],{},[303,1234,683],{},") a módulos por feature (",[303,1237,687],{},[303,1239,690],{},[284,1241,1242],{},"El cambio en sí es simple: mover archivos y actualizar imports. Pero cuando son cientos de archivos en cientos de imports, es la definición de \"trabajo mecánico que nadie quiere hacer un viernes\".",[284,1244,1245,1246,1248],{},"Acá es donde Claude Code se luce. El patrón es uniforme, los cambios son verificables (compila o no), y la única regla nueva es \"todo lo de feature X vive en ",[303,1247,700],{},"\". El agente entiende el patrón al primer ejemplo y lo aplica al resto.",[284,1250,1251],{},"Lo único que hice fue definir el contrato (specs). Qué módulos crear, qué se considera shared, cómo manejar los archivos que tocaba más de una feature. Después fue iterar, validar build, validar tests, ajustar.",[284,1253,1254,1256],{},[323,1255,1216],{}," los refactors estructurales son ideales cuando el patrón es claro pero el volumen es alto. Define las reglas una vez, deja que el agente las aplique en todas partes.",[280,1258,1260],{"id":1259},"caso-3-el-rediseño-de-este-sitio","Caso 3: el rediseño de este sitio",[284,1262,1263,1264,1266,1267,1269,1270,397],{},"El más reciente y el más cercano a este post. Migrar sergioazocar.com desde el diseño anterior al actual: layout edge-to-edge con BentoGrid, Nuxt UI v4, Tailwind v4, sistema de colores custom (",[303,1265,718],{}," y ",[303,1268,722],{},"), tema dark-only, borders translúcidas con ",[303,1271,726],{},[284,1273,1274,1275,1277,1278,1280],{},"La diferencia con los casos anteriores es que el contexto del proyecto era todo mío. Tengo un ",[303,1276,732],{}," cuidado, con las convenciones del repo, el stack, las reglas de i18n, los patrones de blog posts, los gotchas. Cuando le pido a Claude Code que migre un componente al nuevo sistema de borders, no necesito explicarle qué es ",[303,1279,736],{}," ni dónde está definido. Ya lo sabe.",[284,1282,1283],{},"Eso es dogfooding. El proyecto está preparado para que el agente sea útil desde el primer prompt. Y eso paga dividendos rapidísimo, cada turno avanza en lugar de gastarse en re-explicar contexto.",[284,1285,1286,1288,1289,1291],{},[323,1287,1216],{}," invertir en un buen ",[303,1290,732],{}," no es overhead, es la diferencia entre un agente útil y uno que alucina.",[280,1293,1295],{"id":1294},"el-playbook","El playbook",[284,1297,1298],{},"Después de varias migraciones, esto es lo que hago siempre:",[317,1300,1301,1307,1313,1319,1328,1334],{},[320,1302,1303,1306],{},[323,1304,1305],{},"Planifica antes de generar."," Una migración mal scopeada es una migración fallida. Define el alcance, los pasos, los archivos. Si no tienes claridad antes de empezar, el agente tampoco la va a tener.",[320,1308,1309,1312],{},[323,1310,1311],{},"PRs atómicos por fase."," En el caso del toolchain, oxlint y tsgo fueron PRs separados. Si algo se rompe, sabes exactamente qué fase lo causó.",[320,1314,1315,1318],{},[323,1316,1317],{},"Valida en cada paso."," Lint, type-check, tests, preview. Claude Code corre los comandos, tú lees el output. Saltarte la validación porque \"el cambio anterior pasó\" es la receta para acumular 50 errores antes de darte cuenta.",[320,1320,1321,1327],{},[323,1322,1323,1324,1326],{},"Mantén un ",[303,1325,732],{}," actualizado."," Stack, convenciones, comandos, gotchas del proyecto. Es la diferencia entre un agente que entiende el repo y uno que inventa.",[320,1329,1330,1333],{},[323,1331,1332],{},"Mide antes y después."," Sin números no hay historia que contar, ni argumento para venderle al equipo.",[320,1335,1336,1339],{},[323,1337,1338],{},"Pair programming, no autopilot."," El agente no reemplaza criterio, lo amplifica. Tú decides qué se hace, él lo ejecuta y trae el feedback.",[280,1341,1343],{"id":1342},"lo-que-no-hagas","Lo que NO hagas",[357,1345,1346,1352,1358,1364],{},[320,1347,1348,1351],{},[323,1349,1350],{},"No le pidas todo de una vez."," \"Migra el repo entero\" es la peor instrucción posible. Divide en fases, una a la vez.",[320,1353,1354,1357],{},[323,1355,1356],{},"No te saltes la validación."," Aunque el cambio se vea trivial, corre los checks. El error que asumes que no existe es el que termina en producción.",[320,1359,1360,1363],{},[323,1361,1362],{},"No dejes que invente paths o APIs."," Si cita una función que no reconoces, verifica que existe antes de aceptar el cambio.",[320,1365,1366,1369],{},[323,1367,1368],{},"No mergees sin entender los cambios."," El agente acelera tu trabajo, no reemplaza tu responsabilidad. Si no entiendes lo que se cambió, no estás migrando, estás cruzando los dedos.",[280,1371,1373],{"id":1372},"cierre","Cierre",[284,1375,1376],{},"La barrera de entrada para migrar bajó dramáticamente. Lo que antes era un proyecto trimestral hoy es un proyecto de fin de semana. Lo que antes postergabas seis meses, lo puedes iniciar hoy mismo y terminarlo en un par de días.",[284,1378,1379],{},"Si tienes una migración estancada hace tiempo, abre Claude Code esta semana. Probablemente la termines antes de que termine el sprint.",[284,1381,1382],{},"Y aunque las métricas finales no salgan tan lindas, igual vas a entender más de tu codebase en dos días que en los seis meses que llevabas postergándolo.",{"title":96,"searchDepth":15,"depth":36,"links":1384},[1385,1386,1387,1388,1393,1394,1395,1396,1397],{"id":877,"depth":15,"text":878},{"id":890,"depth":15,"text":891},{"id":906,"depth":15,"text":907},{"id":939,"depth":15,"text":940,"children":1389},[1390,1391,1392],{"id":963,"depth":36,"text":964},{"id":1047,"depth":36,"text":1048},{"id":1149,"depth":36,"text":1150},{"id":1220,"depth":15,"text":1221},{"id":1259,"depth":15,"text":1260},{"id":1294,"depth":15,"text":1295},{"id":1342,"depth":15,"text":1343},{"id":1372,"depth":15,"text":1373},"Tres migraciones reales con Claude Code: el toolchain de un monorepo, screaming architecture y el rediseño de este sitio. Lo que antes tomaba semanas, hoy se hace en días.",{},"\u002Fes\u002Fblog\u002Fcomo-hacer-migraciones-con-claude-code-y-no-morir-en-el-intento",{"title":872,"description":1398},{"loc":1400},"es\u002Fblog\u002Fcomo-hacer-migraciones-con-claude-code-y-no-morir-en-el-intento",[862,863,864],"EqJQJcQLgyv3npCjPWhlOIz0neVF-RM4z-sWz076hDU",1777416194600]