Upgrade Next.js 15 to 16: a practical checklist
Updated 2026-11-03
A Next.js 15 app moves to 16 by running the upgrade codemod, then fixing what the codemod does not cover: async params, cookies, and headers; a middleware file that must become proxy unless you still need the edge runtime; and a webpack config that next build will no longer ignore.
The source for this checklist is the Next.js 16 upgrade guide and the upgrading overview. It applies to an app you can run locally: a repository you cloned from Lovable or Bolt, a create-next-app project, or anything else with next in package.json. If you cannot run the app outside the generator, connect GitHub first and follow how to update a Lovable project. Do not upgrade by asking a chat to “bump Next” with no build afterwards.
Check the runtime before you upgrade
node -v
npx tsc --version
npx next --version
git statusNext.js 16 requires Node.js 20.9.0 or newer. Node.js 18 is not supported. TypeScript must be 5.1.0 or newer if the project uses it. The documented browser floor is Chrome 111, Edge 111, Firefox 111, and Safari 16.4. Commit or stash a clean tree so you can diff the codemod. If node -v is older than 20.9, install a current Node before you touch Next. The upgrade will not paper over that.
Run the upgrade codemod from a Next.js 15 app
The overview’s npx next upgrade command exists on Next.js 16.1 and later. A project that is still on 15 does not have that command. Use the codemod package from an older version. Its revision argument lets this checklist target major 16 explicitly instead of a moving latest release:
npx @next/codemod@canary upgrade 16The upgrade guide says this codemod can move next.config to the stable turbopack option, migrate from next lint to the ESLint CLI, rename deprecated middleware to proxy, drop unstable_ prefixes from APIs that stabilized, and remove the experimental_ppr route segment config. It does not run every codemod. If the app still touches params, searchParams, cookies(), headers(), or draftMode() synchronously, which Next.js 15 allowed as a compatibility window, also run:
npx @next/codemod@canary next-async-request-api .If you would rather not run a codemod, the App Router package bump below stays on the Next.js 16 and React 19 major lines:
npm install next@16 react@19 react-dom@19
npm install -D @types/react@19 @types/react-dom@19Use pnpm add or yarn add if that is the lockfile you commit. Mixing npm’s lockfile into a pnpm project is a second problem, covered in why npm audit fix does nothing. The codemod’s --skip-react-upgrade flag keeps the React line where it is. The App Router guidance uses React 19. Keep React, React DOM, and their types aligned, and update eslint-config-next to the 16 line if installed. A Pages Router app should also follow the Pages Router upgrade guide instead of treating every App Router change here as applicable.
If a coding agent will do the rest of the work, the manual upgrade guide includes npx @next/codemod@canary agents-md, which points AGENTS.md at the docs bundled with the installed Next version. On 16.2 and later, next dev keeps that block current when it detects an agent. That file does not replace the build below.
Remove the old Turbopack flags
Turbopack is the default for next dev and next build. The --turbopack and --turbo flags on those scripts are unnecessary. Take them off.
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
}
}experimental.turbopack moves to a top-level turbopack key. The codemod does this when it can. If a custom webpack function is still in next.config, next build fails on purpose so you do not ship a build that ignored that function. The upgrade guide gives three options: migrate the options to Turbopack, run next build --turbopack and accept that the webpack function is ignored, or keep webpack with next build --webpack. A plugin you did not write may be adding the webpack key. If the build says a webpack config was found and you never wrote one, look at the plugins before you delete the error.
A client component that imported a module which imports fs used to be silenced with webpack’s resolve.fallback. Turbopack uses turbopack.resolveAlias for that case. The better fix is to stop importing server-only modules from client files. Do that if you can see the import. Use the alias only when a dependency you do not control pulls fs into the browser graph.
Await request APIs and rename middleware
Synchronous access was a compatibility window in 15. In 16 you await them:
export default async function Page({ params }: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params
return <h1>{slug}</h1>
}The same applies to searchParams, cookies(), headers(), and draftMode(), and to params in generateMetadata and image routes. Generated sitemap functions receive id itself as a promise: resolve it with const resolvedId = await id before using it; it is not a params field. npx next typegen generates helpers such as PageProps<'/blog/[slug]'> so the awaited fields stay typed. Search the codemod’s @next-codemod-error comments and fix those by hand. A comment left in the file is an unfinished step, not a warning you can ignore.
The middleware filename is deprecated and renamed to proxy. Rename middleware.ts to proxy.ts (or the .js pair) and rename the exported function to proxy. Config flags follow: skipMiddlewareUrlNormalize becomes skipProxyUrlNormalize. The upgrade guide is specific about the runtime: proxy runs on Node.js and cannot be configured to use the edge runtime. If you still need edge, keep the middleware file. Do not rename it and then discover auth redirects broke because the edge APIs are gone.
Build, then walk the routes that used to be special
Run the production build. next dev hides failures that next build keeps.
npm run build
npm testWork through what the build prints. These are the breaks the upgrade guide calls out that show up in generated apps:
| What you had | What 16 requires |
|---|---|
next lint in scripts, or a build that linted for you | next lint is removed. next build does not lint. Run ESLint or Biome yourself. The codemod next-lint-to-eslint-cli rewrites the script. |
A parallel route slot with no default.js | Every slot needs a default.js. Return null or call notFound() to keep the old behavior. The build fails without the file. |
revalidateTag('posts') | Pass a cacheLife profile: revalidateTag('posts', 'max'). The one-argument form is deprecated and fails typechecking. Use updateTag in a Server Action when the page must expire immediately. |
unstable_cacheLife and unstable_cacheTag | Import cacheLife and cacheTag from next/cache. The prefix is gone. |
amp: true or next/amp | AMP support is removed. Delete the config and the hook. |
Click the routes that use auth, a rewrite, or a draft preview after the build. Middleware that became proxy is the one most likely to compile and still skip a redirect. A generated app from a chat tool often has this file and nobody remembers writing it.
Dependency advisories that appear because React and Next moved are a separate pass. Clearing them with npm audit fix --force will try to move more packages than this upgrade. See what to do instead of audit fix, and when a bot should open the next pull request.
For weekly upkeep, you can manually create a recurring dependency-review task with Denly’s task schedules after connecting a project and runner. Configure its instructions and review gates before enabling it. The public report form below creates a report; it does not create a schedule or a pull request.
Commands and product behavior on this page were checked against the vendor documentation linked here on 3 November 2026. Those products change their screens. If a label has moved, follow the linked page rather than a memory of this one.
Read next
npm audit fix is not working: why, and what to do instead · Renovate vs Dependabot, and when neither is enough
Denly