1. Why Migrate from Vue to React?
Every engineering team reaches an inflection point where the frontend framework chosen years ago no longer aligns with the direction of the product, the team's hiring pipeline, or the ecosystem they depend on. For many teams, that means moving from Vue.js to React.
The motivations are real and consistent across companies of all sizes:
- Talent pool — React's developer ecosystem is 3–4x larger than Vue's. Hiring becomes easier and onboarding faster when your stack matches where the industry moved.
- Ecosystem depth — The React ecosystem (Next.js, Remix, Tanstack Query, Zustand, Radix UI, shadcn/ui) has compounded for years, offering production-grade solutions for nearly every frontend problem.
- AI tooling alignment — Most AI coding assistants, component libraries, and generative UI tools default to React. Staying on Vue means receiving second-class AI support in 2026.
- Consistency across teams — Organizations with mixed React/Vue codebases pay a steep context-switching tax. Unifying on React reduces cognitive overhead and enables shared design systems.
- Long-term maintenance — Vue 2's end-of-life already happened. Vue 3's Options API to Composition API migration alone was painful for many codebases. React's backward compatibility track record is more predictable.
From a technical standpoint, both frameworks are capable. The decision to migrate is strategic: React offers wider community contributions, better SSR tooling via Next.js, and stronger TypeScript integration out of the box. React Server Components represent a fundamental shift in how frontend architecture works — an innovation with no direct Vue equivalent at scale.
"The question isn't whether Vue can do what React does. It's whether your team, your hiring pipeline, and your AI toolchain are better served by React. For most organizations in 2026, the answer is yes."
2. The Problem with Manual Migration
Manual Vue-to-React migrations are notoriously painful. The two frameworks share the same conceptual model (reactive components, props-down/events-up) but differ in almost every syntactic and structural detail. What looks like a straightforward rewrite quickly becomes a multi-month project filled with regressions and invisible bugs.
The core translation challenges
Every Vue component carries patterns that don't translate mechanically to React:
- Single File Components (SFCs) — Vue's
.vuefiles bundle template, script, and styles. React uses JSX/TSX files with separate CSS modules or CSS-in-JS. The boundary between logic and markup has to be reconceptualized for every component. - Directives to JSX —
v-if,v-for,v-model,v-show, andv-bindhave no direct JSX equivalents. Each requires a mental translation and produces different patterns (&&,map(), controlled inputs, CSS visibility, spread props). - Composition API to Hooks —
ref(),reactive(),computed(),watch(), andonMounted()map roughly touseState,useMemo,useEffect— but the mental model is different enough that developers who are new to React frequently introduce subtle bugs during manual conversion. - Vuex/Pinia to Zustand/Redux — State management APIs are entirely different. Migrating a Vuex store module by module is tedious and error-prone. Pinia's store structure looks deceptively similar to Zustand but has critical behavioral differences around reactivity.
- Vue Router to React Router — Route guards, navigation hooks, route meta fields, and nested route patterns all need to be rebuilt in React Router's API.
The true cost
Beyond the technical complexity, manual migration carries organizational risk:
- Timeline overruns — A codebase of 100 Vue components typically takes 6–12 weeks manually. Any estimates given before actually touching the code are speculative.
- Regression bugs — Without comprehensive tests on the Vue side (which most Vue codebases lack), there is no safety net to confirm the React version behaves identically.
- Knowledge silos — Developers who understand the Vue codebase deeply must now rewrite it in a framework they may be learning simultaneously.
- Feature freeze — Product work halts during the migration window. Business stakeholders pay the cost of frozen development velocity.
- Partial migration debt — Teams that can't finish often end up with a hybrid Vue/React codebase that's worse than either original, combining the maintenance burden of both frameworks.
3. Automated vs Manual Migration: A Comparison
| Dimension | Manual Migration | Automated (Don Cheli /dc:migrate) |
|---|---|---|
| Typical timeline (100 components) | 6–12 weeks | 1–2 weeks |
| Test coverage before merge | Depends on team discipline | Enforced by TDD Iron Law |
| Component conversion | Manual, one by one | Automated analysis + generation |
| State management migration | Rewrite from scratch | Vuex/Pinia → Zustand plan generated |
| Routing migration | Manual route-by-route | Vue Router → React Router mapping |
| Risk of regressions | High (no automated diff) | Low (behavior diff at each step) |
| Feature freeze window | Weeks to months | Incremental, component-by-component |
| Documentation generated | Rarely, post-hoc | Migration report + ADR auto-generated |
| Rollback capability | Manual git revert | Checkpoint-based rollback at each phase |
| Cost | High (senior dev hours) | Framework is open source |
4. How Don Cheli's /dc:migrate Works
Don Cheli's /dc:migrate command is a structured 4-phase migration process that treats the stack migration as a first-class engineering problem — with the same rigor applied to any feature built under the SDD framework. It doesn't blindly convert files; it analyzes, plans, executes incrementally, and verifies at every step.
Phase 1: Analyze
The migration begins with a full codebase analysis. The framework scans your Vue project and produces a migration inventory:
# Start the migration analyzer
/dc:migrate --from vue --to react --analyze
# Output: migration-report.md
# - 87 components found
# - 12 Vuex modules (3 with side effects)
# - 6 Vue Router route guards
# - 4 custom directives requiring manual review
# - 2 render functions (complex, flagged)
# - Estimated migration effort: 9 days
This analysis phase classifies each component by complexity: simple (pure template + props), medium (local state, lifecycle hooks), and complex (render functions, custom directives, deep Vuex integration). This classification drives the execution strategy in Phase 3.
Phase 2: Plan
Based on the analysis, the framework generates a detailed migration plan with dependency ordering. Components with no dependencies are migrated first. Leaf nodes before parent nodes. Shared utilities before components that consume them.
# Generate ordered migration plan
/dc:migrate --from vue --to react --plan
# Creates: migration-plan.json
# {
# "batches": [
# { "batch": 1, "type": "utils", "items": ["dateUtils", "validators"] },
# { "batch": 2, "type": "atoms", "items": ["Button", "Input", "Badge"] },
# { "batch": 3, "type": "molecules", "items": ["SearchBar", "UserCard"] },
# { "batch": 4, "type": "state", "items": ["authStore", "cartStore"] },
# { "batch": 5, "type": "pages", "items": ["HomePage", "CheckoutPage"] }
# ]
# }
The plan also maps third-party Vue dependencies to their React equivalents and flags any that have no direct substitute, requiring manual attention.
Phase 3: Execute
Execution is incremental and test-gated. For each component in the migration plan, the framework follows the TDD Iron Law: write the React test first (RED), generate the React component (GREEN), then verify behavior parity (REFACTOR). No component advances to the next batch until its tests pass.
# Execute migration batch by batch
/dc:migrate --from vue --to react --execute --batch 1
# Per-component flow:
# 1. Generate React equivalent of Vue component
# 2. Write behavior-parity tests (RED)
# 3. Confirm test passes against new component (GREEN)
# 4. Run behavior diff vs. original (VERIFY)
# 5. Checkpoint saved — rollback available if needed
Phase 4: Verify
After all batches complete, a full verification pass runs: integration tests, route coverage, accessibility audit, and a side-by-side behavioral diff of critical user flows. Only a clean verification report triggers the final migration commit.
# Final verification
/dc:migrate --from vue --to react --verify
# Checks:
# [PASS] 87/87 components converted
# [PASS] 312 tests passing
# [PASS] All routes reachable
# [PASS] No OWASP regressions
# [WARN] 1 custom directive requires manual review
# Migration ready for PR.
5. TDD Safety Nets During Migration
The biggest risk in any large-scale migration is the absence of a safety net. You make changes, you think things work, and three weeks later a QA engineer finds a checkout flow regression that was introduced in week one. By then, the diff is enormous and the root cause is hard to isolate.
Don Cheli's TDD Iron Law prevents this by making tests the precondition for migration advancement, not an afterthought. The law is simple and non-negotiable:
"No component is considered migrated until its behavior is proven by a passing test. RED first, then GREEN, then REFACTOR. No exceptions."
Behavior-parity tests
For each Vue component being migrated, the framework generates behavior-parity tests — tests that describe what the component does (not how it does it) and can run against both the Vue original and the React replacement. This creates a shared behavioral contract across the migration boundary.
// Vue original: UserCard.vue
// React target: UserCard.tsx
// Shared behavior-parity test:
describe('UserCard behavior parity', () => {
it('renders user name and avatar', () => {
render(<UserCard name="Ana" avatar="/ana.jpg" />)
expect(screen.getByText('Ana')).toBeInTheDocument()
expect(screen.getByRole('img')).toHaveAttribute('src', '/ana.jpg')
})
it('emits/calls onEdit when edit button is clicked', async () => {
const onEdit = jest.fn()
render(<UserCard name="Ana" onEdit={onEdit} />)
await userEvent.click(screen.getByRole('button', { name: /edit/i }))
expect(onEdit).toHaveBeenCalledTimes(1)
})
})
Incremental checkpoints
Each migration batch creates a git checkpoint with a complete test suite snapshot. If a later batch introduces a regression, the framework can roll back to the last clean checkpoint without losing completed work. This turns a potentially catastrophic all-or-nothing migration into a safe, incremental operation.
The TDD Iron Law also forces developers to understand what each component does before converting it. You cannot write a behavior test for a component you don't understand. This incidental documentation of component behavior is one of the most valuable byproducts of the migration process.
6. Beyond Vue to React: Other Supported Migrations
The /dc:migrate command is not limited to Vue-to-React. It applies the same Analyze → Plan → Execute → Verify methodology to any stack migration where the source and target are well-defined:
- JavaScript to TypeScript — Incrementally adds type annotations, generates
.d.tsfiles for module boundaries, flagsanyusage, and enforces strict mode compliance by batch. Works for Node.js backends, React frontends, and utility libraries. - Angular to React — Converts NgModules, services (DI), and Angular templates to React components and hooks. Particularly useful for organizations moving off Angular 2–15 as those ecosystems age.
- Class Components to Hooks — Converts React class components (
componentDidMount,this.setState) to modern functional components with hooks. A common step for React codebases created before 2019. - Python 2 to Python 3 — Automated
printstatement conversion,unicode/strhandling, integer division,iteritems()/itervalues()replacements, with test parity checks at each module boundary. - REST to GraphQL — Analyzes REST endpoint usage patterns and generates a GraphQL schema with resolvers, along with query/mutation replacements for the client layer.
- CommonJS to ESM — Converts
require()/module.exportstoimport/export, handles circular dependency detection, and updatespackage.jsonmodule configuration. - CSS to Tailwind — Maps existing CSS class patterns to Tailwind utility classes, generating a configuration file that matches your existing design tokens.
Each migration type has a dedicated profile in the framework that understands the semantic differences between source and target, not just the syntactic ones.
7. Step-by-Step: Migrating a Vue App to React
Here's a practical walkthrough of what the migration looks like in practice, using a real-world component as an example.
The Vue source component
<!-- ProductCard.vue -->
<template>
<div class="product-card" @click="handleClick">
<img :src="product.image" :alt="product.name" />
<h3>{{ product.name }}</h3>
<p class="price">{{ formattedPrice }}</p>
<button v-if="inStock" @click.stop="addToCart">Add to Cart</button>
<span v-else class="out-of-stock">Out of Stock</span>
</div>
</template>
<script setup>
import { computed } from 'vue'
import { useCartStore } from '@/stores/cart'
const props = defineProps({
product: { type: Object, required: true },
inStock: { type: Boolean, default: true }
})
const emit = defineEmits(['select'])
const cart = useCartStore()
const formattedPrice = computed(() =>
new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' })
.format(props.product.price)
)
function handleClick() { emit('select', props.product) }
function addToCart() { cart.add(props.product) }
</script>
The React equivalent generated by /dc:migrate
// ProductCard.tsx
import { useCallback } from 'react'
import { useCartStore } from '@/stores/cart'
interface Product {
id: string
name: string
image: string
price: number
}
interface ProductCardProps {
product: Product
inStock?: boolean
onSelect?: (product: Product) => void
}
export function ProductCard({
product,
inStock = true,
onSelect
}: ProductCardProps) {
const addToCart = useCartStore((s) => s.add)
const formattedPrice = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(product.price)
const handleClick = useCallback(() => {
onSelect?.(product)
}, [product, onSelect])
const handleAddToCart = useCallback((e: React.MouseEvent) => {
e.stopPropagation()
addToCart(product)
}, [product, addToCart])
return (
<div className="product-card" onClick={handleClick}>
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
<p className="price">{formattedPrice}</p>
{inStock
? <button onClick={handleAddToCart}>Add to Cart</button>
: <span className="out-of-stock">Out of Stock</span>
}
</div>
)
}
What /dc:migrate handled automatically
- Type annotations — Generated TypeScript interfaces for
ProductandProductCardPropsfrom the Vue prop definitions. - Directive conversion —
v-if/v-elsebecame a ternary expression;:src/:altbecame JSX props;@click.stopbecamestopPropagation(). - Computed to memo — The
computed()property was simplified to an inline expression (not memoized, since it's cheap), withuseCallbackapplied to event handlers that close over stable references. - Pinia to Zustand — The
useCartStoreselector pattern was migrated to Zustand's selector API. - Event emission —
emit('select', product)became an optionalonSelectcallback prop, following React's convention.
Running the full migration sequence
# 1. Install Don Cheli and initialize in your Vue project
npx don-cheli-sdd init
# 2. Analyze the Vue codebase
/dc:migrate --from vue --to react --analyze
# 3. Review the generated migration-report.md
# Make note of flagged items requiring manual review
# 4. Generate the ordered migration plan
/dc:migrate --from vue --to react --plan
# 5. Execute batch 1 (utilities and atoms)
/dc:migrate --from vue --to react --execute --batch 1
# 6. Verify batch 1 before continuing
/dc:migrate --from vue --to react --verify --batch 1
# 7. Continue through remaining batches
/dc:migrate --from vue --to react --execute --batch 2
/dc:migrate --from vue --to react --verify --batch 2
# ... repeat for each batch
# 8. Final full verification
/dc:migrate --from vue --to react --verify --all
# 9. Generate migration ADR and PR description
/dc:migrate --from vue --to react --report
Each --verify step is a hard gate. The framework refuses to advance to the next batch if any test is failing or if the behavioral diff shows a divergence. This is the TDD Iron Law in action: discipline enforced by tooling, not by individuals.
8. Getting Started
Don Cheli SDD is free, open source, and Apache 2.0 licensed. Installation takes under a minute. The migration commands work with Claude Code (full command support), Cursor IDE (via .cursorrules), and any AI agent that reads AGENTS.md.
# Install globally via npx
npx don-cheli-sdd init
# Or clone and install manually
git clone https://github.com/doncheli/don-cheli-sdd.git
cd don-cheli-sdd && bash scripts/instalar.sh --global
# Initialize in your existing Vue project
cd /path/to/your-vue-app
/dc:init --type frontend --stack vue
# Start the migration analysis
/dc:migrate --from vue --to react --analyze
If you're evaluating whether migration makes sense for your specific codebase before committing, the --analyze flag runs in read-only mode and produces an effort estimate with no side effects. It's a free sanity check before you make any decisions.
The full documentation for /dc:migrate, including migration profiles for all supported stack transitions, is available in the GitHub repository.
Stop dreading your stack migration.
Don Cheli's /dc:migrate turns a 3-month manual rewrite into a structured, test-gated, incremental process. Vue to React. Angular to React. JS to TS. Open source, free, and built for teams that can't afford regressions.
Get Don Cheli on GitHub →