Framework Migration Workflow in Cursor
Your React application uses class components, Redux with connect(), React Router v5, and a custom Webpack build. The team wants to move to functional components with hooks, Zustand for state, React Router v7, and Vite. There are 180 components, 45 Redux-connected containers, and 30 route definitions. A full rewrite would take months and halt feature development. A gradual migration lets you keep shipping features while modernizing — but you need a systematic approach to avoid ending up with a codebase that is half old patterns and half new patterns forever.
Framework migrations are one of Cursor’s strongest use cases. The transformations are mechanical (converting class syntax to function syntax, replacing one API with another), the patterns are well-documented, and the volume is high enough that AI assistance pays for itself many times over. The challenge is not the individual transformation — it is orchestrating 180 of them without breaking anything.
What You’ll Walk Away With
Section titled “What You’ll Walk Away With”- A planning workflow that maps your migration scope before changing any code
- Prompts for automated, file-by-file transformation with validation
- A strangler fig strategy that lets you migrate incrementally without a big bang
- Techniques for handling the hardest migrations (state management, routing, build tools)
- Project rules that prevent mixing old and new patterns in new code
Planning the Migration
Section titled “Planning the Migration”Every migration starts with an inventory. You need to know exactly what you are migrating, how many files are affected, and which transformations are needed. Plan mode is built for this.
Plan mode will scan your codebase and produce a structured breakdown. A typical output might look like:
- 95 simple class components (no state, no lifecycle) — auto-migratable
- 50 medium components (state and componentDidMount) — semi-automatic
- 35 complex components (multiple lifecycle methods, shouldComponentUpdate) — needs review
- 45 Redux-connected components — depends on Zustand migration
- 30 route definitions — batch migration after components are done
This inventory becomes your migration roadmap.
The Strangler Fig Pattern
Section titled “The Strangler Fig Pattern”The safest migration strategy is the strangler fig: new code uses new patterns, old code gets migrated incrementally, and both coexist until the migration is complete. Cursor project rules enforce this boundary.
# Migration Rules (Active)
## New Code- All new components MUST be functional components with hooks- All new state management MUST use Zustand stores- All new routes MUST use React Router v7 data loader pattern- No new code may use: class components, Redux connect(), React Router v5 Switch
## Coexistence- Old class components can import new hooks via wrapper functions- Old Redux-connected components can read from Zustand stores via a bridge- Both routing systems run simultaneously via a compatibility layer
## Migration Status- Components: 45/180 migrated (25%)- Redux stores: 2/8 migrated (25%)- Routes: 0/30 migrated (0%)Update the migration status in this rule file as you progress. Agent reads it on every interaction, so it always knows the current state of the migration.
Migrating Components
Section titled “Migrating Components”Simple Components (Stateless)
Section titled “Simple Components (Stateless)”The easiest migration: class components with no state and no lifecycle methods.
For batch migration, you can process multiple files at once:
Migrate these 5 stateless class components to functional components.Apply the same transformation to each:
@src/components/Badge.tsx@src/components/Card.tsx@src/components/Divider.tsx@src/components/Icon.tsx@src/components/Label.tsx
For each file: convert class to function, destructure props, preserve behavior exactly.After migration, run "npm run type-check" to verify TypeScript compilation.Medium Components (Lifecycle Methods)
Section titled “Medium Components (Lifecycle Methods)”Components with state and lifecycle methods require more careful transformation:
@src/components/UserProfile.tsx
Migrate this class component to a functional component:
1. componentDidMount -> useEffect with empty dependency array2. componentDidUpdate(prevProps) -> useEffect with dependency on the specific props3. componentWillUnmount -> useEffect cleanup function4. this.state + this.setState -> useState hooks (one per state variable)5. Instance methods -> useCallback where used as event handlers6. getDerivedStateFromProps -> compute during render or useMemo
Map each lifecycle method to its hook equivalent. Show the before/afterfor each transformation in comments so the reviewer understands the mapping.
Preserve the exact same component behavior. Pay attention to:- State initialization that depends on props- Cleanup logic (timers, subscriptions, event listeners)- Conditional effects based on prop changesComplex Components (HOCs, Render Props)
Section titled “Complex Components (HOCs, Render Props)”Complex components need individual attention:
@src/containers/OrderDashboard.tsx @src/store/ordersReducer.ts
This component uses:- connect() from react-redux with mapStateToProps and mapDispatchToProps- withRouter() HOC from react-router-dom- A render prop from <Query> component for data fetching
Migrate it step by step:1. Replace connect() with a Zustand store hook (create the store if needed)2. Replace withRouter() with useNavigate() and useParams() hooks3. Replace the <Query> render prop with a React Query useQuery hook4. Convert the class to a function component
Create the Zustand store at src/stores/orders.ts that replaces the ReduxordersReducer. It should maintain the same state shape and actions.
Show the migration as a series of commits:- Commit 1: Create Zustand store alongside Redux- Commit 2: Convert component to functional with hooks- Commit 3: Remove old Redux reducer and action filesMigrating State Management
Section titled “Migrating State Management”The Redux-to-Zustand migration is the highest-risk part because state management touches every connected component. Migrate one store at a time.
Migrating Routing
Section titled “Migrating Routing”Router migration is best done as a batch because routes are interdependent:
@src/routes @src/App.tsx
Migrate from React Router v5 to React Router v7:
1. Replace <Switch> with <Routes>2. Replace <Route component={X}> with <Route element={<X />}>3. Replace <Redirect> with <Navigate>4. Replace useHistory() with useNavigate()5. Replace match.params with useParams()6. Replace location.search with useSearchParams()7. Add data loaders for routes that fetch data on mount
Migration steps:- First: Update App.tsx route definitions (batch change)- Then: Update each component that uses routing hooks (one by one)- Finally: Add data loaders to replace componentDidMount API calls
Keep the compatibility layer: if a route uses both old and new patternsduring migration, it should still work.Build Tool Migration
Section titled “Build Tool Migration”Webpack to Vite is typically done in one session because it is configuration-only:
@webpack.config.js @babel.config.js @tsconfig.json @package.json
Migrate from Webpack 4 to Vite:
1. Create vite.config.ts with: - React plugin - TypeScript support - Path aliases matching our current webpack resolve.alias - Proxy configuration matching our current devServer.proxy - Environment variable handling (REACT_APP_ -> VITE_)
2. Update tsconfig.json: - Add Vite client types - Update module resolution if needed
3. Update import.meta.env usage: - Find all process.env.REACT_APP_ references - Replace with import.meta.env.VITE_ - Create .env file with renamed variables
4. Update index.html: - Move from public/ to project root - Add module script tag for entry point
5. Update package.json scripts: - dev: vite - build: vite build - preview: vite preview
List any webpack plugins that need Vite equivalents.Do NOT remove the webpack config yet -- keep it as fallback until verified.When This Breaks
Section titled “When This Breaks”Agent migrates code incorrectly and tests pass because tests are also outdated. This is the most dangerous migration bug. Before migrating a component, verify that its existing tests actually exercise the behavior you care about. Ask Agent: “Before migrating this component, review its test file. Do the tests cover the key behaviors, or are they just checking that it renders?”
The migration bridge causes performance issues. Syncing state between Redux and Zustand on every change creates unnecessary re-renders. Keep the bridge simple (one-directional sync) and remove it as soon as all consumers of that store are migrated.
Half-migrated code confuses Agent. When your codebase has both old and new patterns, Agent sometimes generates code using the old pattern because it sees more examples of it. The migration rules file at the top of this article prevents this — but only if it is set to “Always Apply.”
The migration stalls at 80%. The last 20% of components are always the hardest because they are the most complex. Do not leave them. Schedule dedicated migration sprints and use Cursor’s Plan mode to break each complex component into smaller, manageable steps.
Rollback is needed after partial migration. Keep the old code paths available (commented out or behind feature flags) until the migration is verified in production. Cursor’s checkpoints help with per-session rollbacks, but for multi-day migrations, use Git branches.