# Upgrade Next.js 13 to Next.js 16

Yes. An agent takes a Next.js 13 app to 16 step by step from the upgrade guide, keeps the build and lint green at each step, and lists what needs a person to decide.

[Run this on Rush](https://cloud.getrush.ai/run/upgrade-framework-major/nextjs/), in a Project that has your repository.

## 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`

```json
{
  "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"
    }
  ]
}
```

1. **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.
2. **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.
3. **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.

## 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.
