I've been wrangling React codebases professionally for well over ten years now, and honestly, the story is always the same in 2026: teams inherit these massive, everything-in-one-place apps built back when Create React App felt like the future. All the logic — auth, shopping cart, product lists, user profiles — lives in a handful of giant files. Props get drilled six levels deep, the state is scattered, and nobody wants to touch it because one wrong move brings the whole thing down. Last year, I led a refactor on a five-year-old dashboard exactly like that. We managed to break it into proper feature slices and even laid the groundwork for microfrontends. The thing that made the biggest difference? A multi-agent AI setup that did a lot of the heavy lifting for us. It wasn't magic — it still needed human eyes — but it turned a three-month nightmare into something we wrapped in five weeks. In this piece, I'll walk you through how I built that system. We'll take a messy little React monolith (the kind you see everywhere) and let a team of AI agents analyze it, plan the refactor, write the new modular code, add tests, and review everything. We'll use LangGraph to orchestrate the agents and Claude 3.5 Sonnet as the LLM (though GPT-4o works fine too). What You'll Need Nothing exotic: Node 20+ and your package manager of choice. Python for the agent orchestration (LangChain/LangGraph live there — it's still the most reliable option). An Anthropic API key (or OpenAI). Just export it as ANTHROPIC_API_KEY. Git and VS Code. I lean heavily on the Cursor extension these days for quick diff reviews. Grab the sample app we'll be working with — a tiny e-commerce dashboard where login, product list, and cart are all crammed into src/App.js. It's deliberately ugly, but painfully realistic. Here's the heart of the mess: JavaScript import React, { useState } from 'react';
import './App.css';

function App() {
  const [user, setUser] = useState(null);
  const [cart, setCart] = useState([]);
  const [products] = useState([{ id: 1, name: 'Widget', price: 10 }]);

  const login = (username, password) => {
    if (username === 'admin') setUser({ username });
  };

  const addToCart = (product) => {
    setCart([...cart, product]);
  };

  return (
    <div className="App">
      {!user ? (
        <form onSubmit={(e) => { e.preventDefault(); login(e.target.username.value, e.target.password.value); }}>
          <input name="username" placeholder="Username" />
          <input name="password" type="password" />
          <button>Login</button>
        </form>
      ) : (
        <>
          <h1>Welcome, {user.username}</h1>
          <div>
            <h2>Products</h2>
            {products.map(p => (
              <div key={p.id}>
                {p.name} - ${p.price}
                <button onClick={() => addToCart(p)}>Add to Cart</button>
              </div>
            ))}
          </div>
          <div>
            <h2>Cart ({cart.length})</h2>
            {/* cart items would go here */}
          </div>
        </>
      )}
    </div>
  );
}

export default App; You get the idea: everything lives in one component, auth is fake and insecure, no routing, no code splitting. Why Legacy React Apps Are Such a Pain Most big companies are still running apps that started life pre-React 18. Giant components, prop drilling everywhere, bundle sizes that make mobile users cry. Adding a new feature means touching half the codebase and praying the tests (if they exist) still pass. Agentic workflows help because they can read the whole thing at once, spot patterns we miss when we're deep in the weeds, and churn out consistent modular code faster than any human could. The Agent Team I run five specialized agents that hand work off to each other: Analyzer – reads the code and produces a structured report. Planner – turns that report into concrete steps. Coder – writes the actual refactored files. Tester – generates meaningful tests. Reviewer – catches anything that slipped through. The Analyzer we already made pretty thorough in the last version. Let's spend more time on the two that do the real work: Coder and Tester. Coder Agent This is the one that actually moves code around. I've learned the hard way that vague prompts lead to broken imports and forgotten lazy loading, so I lock it down pretty tight. Here's the system prompt I use: Python coder_prompt = ChatPromptTemplate.from_messages([
    ("system", """You're a senior React engineer whose specialty is cleaning up old monoliths.

Implement the refactor plan exactly—no creative detours. Rules I always follow:
- Functional components and hooks only.
- Feature-sliced layout: src/features/auth/, src/features/products/, src/features/cart/
- React Router v6+ with proper <Routes> and <Route>
- Every route component wrapped in React.lazy() + Suspense for code splitting
- Shared state lives in dedicated contexts under src/context/
- Forms are fully controlled (no e.target.username nonsense)
- Components stay small and focused
- Relative imports must be correct in the new structure
- Don't add new dependencies unless the plan explicitly says so

Output must be a JSON object: keys are full file paths, values are complete file contents. Include every new or changed file. Nothing else."""),
    ("user", """Analysis JSON: {analysis_json}
Original files: {original_files}
Plan: {plan}""")
]) Tester Agent Good tests are what keep me from losing sleep after a refactor. The tester prompt forces realistic RTL/Jest tests: Python tester_prompt = ChatPromptTemplate.from_messages([
    ("system", """You're a frontend testing specialist. Write clean, useful tests with React Testing Library and Jest.

For every important new or changed component:
- Test rendering and key interactions
- Use proper roles and accessible queries
- Mock contexts when needed
- Include at least one error/empty state test where it makes sense
- Keep tests focused—aim for meaningful coverage, not 100% theater

Output JSON: keys are test file paths (e.g. src/features/auth/LoginForm.test.jsx), values are full test files."""),
    ("user", "Refactored files: {refactored_files}")
]) What Happens When We Run It Feed the original App.js into the workflow. The Analyzer spots the usual suspects — high-severity coupling, oversized component, no code splitting, insecure auth — and gives us a nice JSON plan. Coder takes that plan and produces things like: A proper LoginForm.jsx with controlled inputs Separate ProductsList.jsx and Cart.jsx Context providers for auth and cart An AppRoutes.jsx that looks roughly like this: JavaScript import React, { Suspense } from 'react';
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';

const LoginForm = React.lazy(() => import('./features/auth/LoginForm'));
const ProductsList = React.lazy(() => import('./features/products/ProductsList'));
const Cart = React.lazy(() => import('./features/cart/Cart'));

function AppRoutes() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div>Loading...</div>}>
        <Routes>
          <Route path="/login" element={<LoginForm />} />
          <Route path="/products" element={<ProductsList />} />
          <Route path="/cart" element={<Cart />} />
          <Route path="*" element={<Navigate to="/login" />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

export default AppRoutes; Tester then writes solid tests — one of my favorites from a real run: JavaScript import { render, screen, fireEvent } from '@testing-library/react';
import LoginForm from './LoginForm';
import { AuthContext } from '../../context/AuthContext';

const renderWithContext = (ui, { user = null, login = jest.fn() } = {}) => {
  return render(
    <AuthContext.Provider value={{ user, login }}>
      {ui}
    </AuthContext.Provider>
  );
};

test('submits credentials correctly', () => {
  const mockLogin = jest.fn();
  renderWithContext(<LoginForm />, { login: mockLogin });

  fireEvent.change(screen.getByPlaceholderText('Username'), { target: { value: 'admin' } });
  fireEvent.change(screen.getByLabelText(/password/i), { target: { value: 'secret' } });
  fireEvent.click(screen.getByRole('button', { name: /login/i }));

  expect(mockLogin).toHaveBeenCalledWith('admin', 'secret');
}); The Reviewer usually asks for one or two small tweaks (like adding a redirect after login), we loop back to Coder, and we're done. Running the Tests and Shipping npm test on the generated suite usually passes after the first or second iteration. Bundle size drops noticeably once the lazy loading is in place. I still review every diff in Cursor — AI doesn't get a free pass — but the volume of clean, consistent code it produces is night-and-day compared to doing it all manually. Lessons From the Trenches The detailed, structured prompts are what make this actually usable in real projects. Loose instructions = chaos. JSON output with file paths = easy automation. We've used this pattern on much larger apps (10–15k lines) and consistently needed only minor manual fixes afterward. Important Caveats If You're Thinking of Running This on Your Own Monolith Look, this setup works great on small-to-medium apps (a few hundred to a couple thousand lines), and it's a fantastic way to prototype a refactor or clean up a prototype. But before you point it at your company's million-line dashboard, here are the realities I've run into: Token limits are real. Even Claude 3.5's 200k context window fills up fast on anything bigger than a modest app. You'll need to chunk the codebase — feed in one feature or directory at a time — or build smarter retrieval tools (like vector search over your repo). Full-app refactors in one shot just aren't feasible yet. Hallucinations and subtle bugs happen. The agents are good, but they can invent imports that don't exist, miss edge cases in business logic, or subtly change behavior. Never merge without a thorough human diff review. In our bigger projects, we treat the AI output as a very smart PR draft, not final code. Costs add up. Running multiple agents with long contexts on a large codebase can burn through hundreds of dollars in API credits quickly. Start small and monitor usage. Non-code concerns get ignored. Package.json changes, build config, environment variables, and custom webpack setups — these agents won't touch them unless you explicitly add tools for it. It's best for mechanical refactors. Extracting components, adding routing, introducing contexts, code splitting — these are where it shines. Complex domain logic migrations or performance optimizations still need heavy human involvement. Top-tier companies are experimenting, not relying. Places like Meta, Google, and Amazon are piloting agentic workflows internally, but they're wrapping them in heavy guardrails, custom retrieval systems, and mandatory review gates. Full autonomy on critical monoliths isn't happening yet — think 30–50% productivity boost on targeted tasks, not full replacement. Use this as an accelerator, not a silver bullet. Start with one bounded feature, let the agents propose the changes, review and tweak, then expand. That's how we've gotten real wins without disasters. Wrapping Up If you're staring at a legacy 0 right now, give this approach a shot. It's not about replacing engineers — it's about letting us focus on the hard problems instead of endless boilerplate and busywork. I'd love to hear what your biggest React refactor headache is at the moment. Drop it in the comments — maybe we can figure out how to tackle it next. Happy (and much less painful) refactoring!
==============
В тексте описывается методика использования AI-агентов для рефакторинга устаревших React-приложений. Система, построенная на LangGraph и LLM (например, Claude 3.5 Sonnet или GPT-4o), анализирует код, предлагает план рефакторинга, генерирует новый, модульный код, добавляет тесты и проводит их проверку. Для этого требуется Node.js, Python (для LangChain/LangGraph), API-ключ к Anthropic или OpenAI. Система состоит из пяти специализированных агентов: Анализатора, Планировщика, Кодера, Тестера и Рецензента. Главная идея — автоматизировать рутинные задачи, такие как добавление маршрутизации, code splitting, создание контекстов и тестов, чтобы повысить продуктивность разработчиков. Несмотря на эффективность, автор подчеркивает, что подход требует тщательной проверки и не предназначен для полного автономного рефакторинга, особенно в крупных и критически важных проектах.
