What the agent does
The agent first measures how far the app is from current. In an app like shadcn-ui/taxonomy, package.json pins next to 13.3.2-canary.13 with React 18.2, next-auth 4.22.1 and @next-auth/prisma-adapter, Prisma 4.13 on MySQL, contentlayer 0.3 with next-contentlayer, Tailwind 3.3, ESLint 8 with eslint-config-next 13.0.0, TypeScript 4.7.4 and @types/react 18.0.15; .nvmrc says Node 16.18, pnpm-lock.yaml says pnpm, and the scripts are dev (concurrently "contentlayer dev" "next dev"), build (contentlayer build && next build), lint (next lint) and a postinstall of prisma generate. next.config.mjs wraps withContentlayer around experimental.appDir: true, experimental.serverComponentsExternalPackages and images.domains. There are no tests and no CI workflow; husky runs pretty-quick on commit.
It reads the code for the patterns the upgrade changes. Route handlers such as app/api/posts/[postId]/route.ts read context.params synchronously through a zod schema, and pages such as app/(marketing)/blog/[...slug]/page.tsx read params.slug directly; from Next.js 15 those are promises, so each is awaited and the codemod next-async-request-api does most of it. middleware.ts uses withAuth from next-auth/middleware. NextAuth is wired twice, in pages/api/auth/[...nextauth].ts and a disabled app/api/auth/[...nextauth]/_route.ts, so the app has both routers; the agent keeps the Pages handler working or enables the App Router handler and deletes the other, and names that choice. app/layout.tsx already uses next/font, and env.mjs validates every secret with @t3-oss/env-nextjs, which means next build needs a .env.local with non-empty placeholders to get past validation.
The upgrade goes one major at a time, with pnpm build and pnpm lint after each. Node first, since Next.js 16 does not run on Node 16: .nvmrc moves to a supported Node 20 release. Then next@14 with experimental.appDir removed, next@15 with React 19, @types/react 19, serverComponentsExternalPackages renamed to serverExternalPackages, images.domains rewritten as remotePatterns and the async request API codemod applied, then next@16, where next lint is gone and the lint script calls ESLint directly with the flat config eslint-config-next now ships, and the middleware.ts convention gives way to proxy.ts. eslint-config-next and @next/codemod run at each matching major (npx @next/codemod@canary upgrade does the version bumps and offers the codemods).
Some steps are decisions, and the agent lists them rather than guessing. contentlayer 0.3 stopped releasing before Next.js 14, so the MDX pipeline either moves to the community fork contentlayer2 with next-contentlayer2 or is left for a person; next-auth 4 on React 19 and the App Router works for this app's email and GitHub providers but Auth.js 5 is the supported line; Prisma 4 to 6 and Tailwind 3 to 4 are independent majors the prompt does not ask for, so they stay. TypeScript 4.7 cannot type-check Next.js 16's types, so it moves to 5 in the same step. Each step is its own commit, the build output after each is in the record, and the description ends with the decisions list.
Start it with one API call
POST https://api.prix.dev/o/{org}/sessions with this body:
{
"agent": "claude",
"input": "Upgrade this app from Next.js 13 to Next.js 16 one major version at a time, following the official upgrade guides and running the official codemods where they apply. The repository has no tests, so after each step run pnpm build and the lint script, and fix what breaks before moving on. Keep each step in its own commit. Where an upgrade depends on a library that has no release for the new Next.js, or on a choice between two supported paths, do not guess: finish what can be finished and list the decision. Open a pull request whose description lists each step, the build result after it, and everything left for a person to decide.",
"resources": [
{
"url": "https://github.com/your-org/your-app"
}
]
}- Get an API key. In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
- Send the request. POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
- Read what came back. Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.
What comes back
What comes back is a pull request on a new branch with one commit per step: Node and TypeScript, Next.js 14, Next.js 15 with React 19 and the async request APIs, Next.js 16 with the lint script and the proxy.ts rename. The diff touches package.json, pnpm-lock.yaml, .nvmrc, next.config.mjs, tsconfig.json, the route handlers and pages that read params, and the middleware file. Content under content/ and components under components/ui/ are unchanged unless a type error forced a one-line fix.
The description is a table of steps with the pnpm build and lint result after each, then the decisions: the MDX pipeline (contentlayer fork or replacement), the NextAuth handler location and the Auth.js 5 question, and any dependency it pinned to make the build pass. The trajectory keeps every build log, including the ones that failed before a fix.
Before merging, a reviewer runs the app locally against a database, signs in with the email provider and opens the editor, since no test covers them, checks that generateStaticParams pages still prerender, that the Stripe webhook route still verifies signatures with the awaited headers(), and reads each decision before deciding which follow-up pull requests to open.
Run it on every framework major release
Create an automation in the Project with this prompt and a schedule trigger that runs once a month, with the target version read from the prompt or left as latest. Most months the run finds nothing to do and ends without a pull request; when a major lands, the pull request arrives with the steps already separated and the decisions listed.
Give the API key a webhook URL to receive run.completed and post the pull request link to the frontend channel. For a fleet of Next.js apps, start one run per repository; each sandbox installs that app's own lockfile and works through its own list.
The use case this job follows, with its automation and trigger: Upgrade dependencies weekly.
Why run it on Rush?
Three major versions of Next.js in one sitting means a day of reading upgrade guides, running codemods, and reading build errors about async params and renamed config keys, on a laptop that cannot do anything else while next build runs. On Rush the agent does that in its own sandbox with the project's pnpm and a pinned Node, and every build log is in the record.
The result is a pull request where each step is a commit and each build after it is quoted, so a reviewer can trust the mechanical parts and spend their attention on the decisions the agent listed instead of made. The same request runs on every app that is still on an old major, at the same time.
Frequently asked questions
There are no tests. How do I know the app still works?
The build, the lint script and the type check are the automatic proof, and the record shows them after every step. The description tells you which flows have no coverage (sign-in, the editor, the Stripe webhook), so the reviewer checks those by hand or asks for a follow-up run that adds tests.
Will it upgrade Prisma, Tailwind and ESLint too?
Only as far as Next.js 16 requires: ESLint moves because next lint is gone and eslint-config-next ships a flat config, TypeScript moves because 4.7 cannot read the new types. Prisma 4 and Tailwind 3 keep building and stay, listed as separate upgrades.
What happens to contentlayer?
The agent tries the community fork, contentlayer2 with next-contentlayer2, which keeps the same defineDocumentType config and generated imports. If the build passes, that step is a commit with the swap named; if not, the step is left for a person and the MDX routes are marked as the reason the final step did not complete.
Does it need a database to build?
prisma generate runs without one and the marketing, docs and blog pages are static, but env.mjs refuses empty variables, so the agent writes placeholder values into .env.local for the build. Dashboard pages render at request time and are not hit during the build.
Can it stop at Next.js 15 instead?
Yes. Change the target in the prompt and the run finishes at that step, with the same per-step commits and the same decisions list up to there.