Mass Dependency Upgrade on a 42k-Line Codebase
When the time came to update the dependencies on kentcdodds.com—a project with over 42,000 lines of code and 330,000 words of content—the upgrade list was substantial. The first step was getting a clear picture of the scope. Using Cursor Composer 1.5, a single prompt produced a categorized inventory of all outdated packages, sorted by upgrade difficulty.
| Package | Current | Latest |
|---|---|---|
| @conform-to/react | 1.16.0 | 1.17.1 |
| @conform-to/zod | 1.16.0 | 1.17.1 |
| @epic-web/config | 1.21.3 | 1.24.0 |
| @modelcontextprotocol/sdk | 1.25.3 | 1.26.0 |
| @playwright/test | 1.58.1 | 1.58.2 |
| @prisma/adapter-better-sqlite3 | 7.3.0 | 7.4.0 |
| @prisma/client | 7.3.0 | 7.4.0 |
| @tailwindcss/vite | 4.1.18 | 4.2.0 |
| @types/react | 19.2.10 | 19.2.14 |
| esbuild | 0.27.2 | 0.27.3 |
| framer-motion | 12.29.2 | 12.34.2 |
| isbot | 5.1.34 | 5.1.35 |
| metascraper | 5.49.21 | 5.49.24 |
| metascraper-description | 5.49.21 | 5.49.24 |
| metascraper-image | 5.49.21 | 5.49.24 |
| metascraper-title | 5.49.21 | 5.49.24 |
| msw | 2.12.7 | 2.12.10 |
| prisma | 7.3.0 | 7.4.0 |
| tailwindcss | 4.1.18 | 4.2.0 |
| vite-plugin-cjs-interop | 2.4.0 | 2.4.1 |
The analysis broke down as follows: 19 packages in the "easy" category (patch and minor updates within the same major version), 42 in the "medium" tier (single major version bumps with migration guides to consult), and 14 in the "hard" group (multiple major versions or framework-critical changes). The hard category included Remix, Vite/Vitest, XState, and Zod—each with their own migration concerns. Remix had patches in other/patches/ for @remix-run/react, @remix-run/router, and react-router-dom that would need re-checking. Vite 5→7 and Vitest 1→4 were large jumps requiring config and plugin changes. XState 4→5 and @xstate/react 3→6 involved major API changes, and Zod 3→4 was a breaking change that would affect many consumers such as Conform and Remix.
| Package | Current | Latest |
|---|---|---|
| @epic-web/config | 1.21.0 | 1.24.0 |
| prettier | 3.5.3 | 3.8.1 |
| wrangler | 4.19.1 | 4.67.0 |
| zod | 3.25.57 | 3.25.76 (within v3) |
A week earlier, the Tailwind v3→v4 upgrade had already been handled through Cursor, so that update wasn't on this list. With the inventory in hand, the easy upgrades went first, executed by forking the conversation and instructing the agent to update all of them. Those went without issue. The medium-tier upgrades came next—a few problems surfaced, but the agent largely handled the breaking changes itself. This approach only works because of solid test coverage and documentation created during the project's initial development.
Handling Major Version Jumps
The major changes were tackled one at a time for better control. Vite and Vitest were grouped together and upgraded without incident. The Zod upgrade came next, with specific guidance: "Conform has a special export for zod 4 integration. Use that." That experience from prior Epic Stack migrations made the process smooth.
XState was handled in a fresh conversation, since the @xstate/react v3→v6 upgrade was flagged as significant. The xstate-dependent code is roughly 200 lines—used for the in-browser Call Kent Podcast recorder—but was originally challenging to write. The instruction was to look up migration docs and follow them. Cursor searched the web, found the relevant documentation, planned the migration, and executed it completely. Because this was a separate conversation, the care given to this upgrade was justified by its complexity.
The Remix to React Router v7 Transition
The largest change—Remix v2 to React Router v7—is technically a dependency upgrade rather than a framework migration, since Remix v2 was essentially a wrapper around React Router. The Remix team's future flags were designed to enable incremental adoption of new features, avoiding a big-bang upgrade. None of those flags had been enabled, which made this a substantial undertaking.
For this one, the new long-running background agent feature in Cursor was the right tool. The prompt was detailed:
Upgrade us from Remix v2 to React Router v7. Before doing this, look up relevant migration docs/upgrade guides. We have some remix-specific dependencies as well. Make sure their latest versions work with React Router. If they don't, find alternatives.
We have some patch files. These are to make it so we can add headers to remix fetchers and things. I don't think that feature will ever exist so I want you to delete those patch files and instead find an alternative solution (a simple fetch with a revalidation call should be sufficient, but double check my thinking on that).
Additional requirements were passed along: adopting react-router-auto-routes (which would require a substantial file move), removing the unnecessary REMIX_DEV_HTTP_ORIGIN, and keeping a manual fetch approach to skip buffering of request bodies in a server that can't act on the data anyway.
Routing was a deliberate area of change. Previously the project used remix-flat-routes, but react-router-auto-routes—the current choice in the Epic Stack—was the target. This was arguably a risky move since framework upgrades usually warrant minimal changes, but the decision was to let the agent handle it.
The first run hit the expected snag: the routing convention change confused the agent. The distinction was made clear by comparing the npx react-router routes output on the current branch against the npx remix routes output on main. The required outcome was a directory structure that made react-router-auto-routes match the original routing structure as closely as possible—order and file paths could differ, but the basic structure had to be nearly identical. After referencing documentation for both routing libraries, the agent corrected the structure on the next attempt.
With routing working, the agent was still being conservative. An overnight run was kicked off with instructions to modernize type handling in Loaders, actions, and components per the current React Router docs—no approval needed, just execution:
Could you adopt the latest on how to handle types in Loaders, actions, components, etc based on the react router documentation? You can make a plan but don't wait for me to approve it. Just go for it. I'm going to sleep and want this all to be done when I wake. You got this!
The background agent ran for 21 minutes. During that time, it iterated several times with Cursor BugBot. With auto-fix enabled, BugBot issues triggered new agents to fix problems, and the process managed itself well. The next morning, the changes were pulled down and tested. Both BugBot and CodeRabbit had flagged additional issues, so agents kept fixing things as they were found. After more iterations, the migration was merged into main.
The Agent-Driven Workflow
The results speak to an evolving workflow. For a dedicated developer, running multiple agents across different projects—local Cursor agents alongside cloud agents—has become routine. The pattern of orchestrating agents for distinct tasks, giving them the ability to check their own progress and iterate on failures, produces a capable setup.
The volume of prompts reflects how tightly integrated this workflow has become. Agent-triggered prompts number in the hundreds daily; on the day of the site migration alone, there were 25 self-triggered agents. Some handle small tasks, others larger ones, and multiple projects run in parallel.
The new workflow has its own hazards, though. The constant stream of notifications from local Cursor instances and email updates from cloud agents makes it hard to fully disconnect. An inbox-zero mindset fights against the pull of the next prompt, and stepping away for lunch or family time becomes a deliberate act. Each next problem is only a prompt away, and that proximity is both the strength and the danger of this way of working.



