Upgrading a Next.js SaaS from 15 to 16: What Actually Breaks

Updated July 2026 · ~6 min read

Next.js 16 is a smaller jump than 14→15, but it does remove things that version 15 only deprecated. This is a field guide from upgrading a real multi-tenant SaaS (Next.js 15.5 → 16.2): what the codemod handles for you, the one breaking change that touches actual application code, and a generated-code gotcha that sends people chasing TypeScript errors that were never Next.js's fault.

Step 0: run the codemod

Don't hand-edit what a tool can do. The official upgrade codemod bumps your dependencies and applies most mechanical migrations:

npx @next/codemod@canary upgrade latest

It will update next, react, and react-dom, and offer to run the individual transforms below. Commit first so the diff is reviewable.

Breaking change #1: Request APIs are now async-only

This is the one that matters. Next.js 15 let you call cookies(), headers(), draftMode(), and read params / searchParams synchronously (with a warning). Next.js 16 removes synchronous access entirely — these are now async and must be awaited (or unwrapped with React.use() in Client Components).

// Next.js 15 (worked, with a deprecation warning)
const token = cookies().get('token')

// Next.js 16 — await it
const token = (await cookies()).get('token')

// In a Server Component page, params/searchParams are Promises too:
export default async function Page({ params }) {
  const { slug } = await params
}

There's a codemod for this specifically — it rewrites most call sites for you:

npx @next/codemod@canary next-async-request-api .
⚠️ For multi-tenant SaaS this touches your hottest path: anywhere you resolve the session/tenant from cookies() or headers() now needs await. Grep for cookies( and headers( after the codemod and eyeball each one — the transform is good but a missed site fails at runtime, not build.

Breaking change #2: middleware.jsproxy.js

The middleware file convention was renamed. If you have a root middleware.ts (auth gate, redirects), rename it — again, there's a codemod:

npx @next/codemod@canary middleware-to-proxy .

Config change: Turbopack moved to the top level

Turbopack config graduated out of experimental. Move it in next.config.ts:

// before (15)            →  after (16)
// experimental: {           turbopack: {
//   turbopack: { ... }        ...
// }                        }

The gotcha nobody warns you about: stale generated code

After the upgrade our type check lit up with errors that looked like Next.js or React type regressions. They weren't. They were a stale Prisma client — the generated types in node_modules/.prisma predated the dependency bump. The fix is boring and total:

npx prisma generate      # regenerate the DB client
# ...and any other codegen you run (GraphQL, tRPC, i18n, etc.)
npx tsc --noEmit         # now the phantom errors are gone

Rule of thumb after any major framework bump: regenerate before you debug. More than half of "the upgrade broke my types" reports are stale codegen, not the framework.

Verify, in order

  1. npx tsc --noEmit — types clean (after regenerating codegen).
  2. next build — the real gate; confirm every route still compiles.
  3. Smoke-test the auth/tenant flow — because the async Request API change hits it hardest and can fail at runtime, not build.
🛠 Start on Next.js 16, not behind it — SaaS Starter →
A production-ready Next.js 16 boilerplate: org-scoped multi-tenancy, RBAC, signature-verified Stripe billing, hashed API keys, and audit logs — already on the async Request APIs. Free Lite on GitHub; full version one-time $99.

Should you upgrade now?

Yes, if you're actively building — the async Request API change only gets more expensive to defer as your codebase grows, and buyers of dev tooling notice a stale major version. If you're mid-launch and frozen, do it right after. The codemod plus a regenerate-and-rebuild pass is usually an afternoon, not a week.

Recap

  1. Run @next/codemod upgrade latest; commit first.
  2. await all Request APIs (cookies/headers/draftMode/params/searchParams) — use the next-async-request-api codemod, then eyeball auth/tenant call sites.
  3. Rename middlewareproxy; move Turbopack config top-level.
  4. Regenerate Prisma/other codegen before debugging type errors.
  5. tsc --noEmitnext build → smoke-test auth.