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.
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.
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 .
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.middleware.js → proxy.jsThe 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 .
Turbopack config graduated out of experimental. Move it in next.config.ts:
// before (15) → after (16)
// experimental: { turbopack: {
// turbopack: { ... } ...
// } }
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.
npx tsc --noEmit — types clean (after regenerating codegen).next build — the real gate; confirm every route still compiles.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.
@next/codemod upgrade latest; commit first.await all Request APIs (cookies/headers/draftMode/params/searchParams) — use the next-async-request-api codemod, then eyeball auth/tenant call sites.middleware → proxy; move Turbopack config top-level.tsc --noEmit → next build → smoke-test auth.