On this page · 14 sections
- What will break, ranked by how fast it stops you
- Start with the codemod, then read what it missed
- Async request APIs: the change that touches every route
- Turbopack by default, and the build that fails on purpose
- Cache Components, and why caching is now opt-in
- The caching API changes that produce type errors
- middleware.ts becomes proxy.ts, and loses the Edge runtime
- The next/image defaults nobody reads until something looks wrong
- Removals and quiet behaviour changes
- India-specific considerations
- What we would sequence, and in what order
- FAQ
- How eCorpIT can help
- References
Summary. Next.js 16 shipped on 21 October 2025, and the documentation now tracks 16.2.10 as of 13 May 2026. It is the release that stops being polite about deprecations. Synchronous access to cookies(), headers(), draftMode(), params and searchParams is fully removed, having been merely deprecated in Next.js 15. Turbopack becomes the default bundler for both next dev and next build, and if you have a custom webpack configuration your production build will fail outright rather than warn. middleware.ts is renamed to proxy.ts, and the Edge runtime is not supported in proxy. Node.js 18 is dropped, with 20.9.0 the new minimum, alongside TypeScript 5.1 and Chrome, Edge and Firefox 111 or Safari 16.4. Four next/image defaults changed, next lint and AMP are gone, and every parallel route slot now needs an explicit default.js or the build fails. The images.imageSizes default drops the 16px entry, which Vercel says only 4.2% of projects used. Vercel reports Turbopack delivering 2 to 5 times faster production builds and up to 10 times faster Fast Refresh, and said more than 50% of development sessions and 20% of production builds on Next.js 15.3 and later were already running on Turbopack before the release. That speed is the payment for the work below.
What will break, ranked by how fast it stops you
Most upgrade guides list changes alphabetically. That is not the order you hit them in. This is:
| Breaking change | Fails at | Codemod? | Effort |
|---|---|---|---|
Custom webpack config with next build |
Build, immediately | No, use --webpack flag |
Minutes to decide, weeks to migrate |
Parallel route slots missing default.js |
Build | No | Minutes per slot |
Sync cookies(), headers(), params, searchParams |
Type check and runtime | Yes, next-async-request-api |
Hours, mostly automated |
middleware.ts still named that |
Deprecation, then removal later | Yes, the v16 upgrade codemod |
Minutes |
revalidateTag() with one argument |
TypeScript error | No | Hours, needs judgement |
| Node.js 18 runtime | Install or CI | No | Depends on your platform |
Work top-down. The first two stop the build with no output. The third produces a wall of type errors that looks catastrophic and is mostly mechanical.
Start with the codemod, then read what it missed
Vercel ships an automated upgrade path, and it does more than bump a version number. From the official version 16 upgrade guide:
# Automated upgrade
npx @next/codemod@canary upgrade latest
# Or manually
npm install next@latest react@latest react-dom@latest
The v16 codemod updates next.config.js to the new turbopack configuration, migrates next lint to the ESLint CLI, migrates the deprecated middleware convention to proxy, removes the unstable_ prefix from stabilised APIs, and removes the experimental_ppr route segment config. That covers a genuine majority of the mechanical work.
For the async request APIs specifically, run the dedicated codemod:
npx @next/codemod@latest next-async-request-api .
Where an automatic migration is not possible, the codemod adds a typecast in TypeScript files or a comment, and leaves it for you. Those comments are the actual work. Do not merge the codemod's output without reading them.
Async request APIs: the change that touches every route
Next.js 15 introduced async request APIs with temporary synchronous compatibility. Next.js 16 removes that compatibility entirely. The affected surface is wide: cookies, headers, draftMode, params in layout.js, page.js, route.js, default.js, opengraph-image, twitter-image, icon and apple-icon, and searchParams in page.js.
// Next.js 15 - synchronous access, deprecated
import { cookies } from 'next/headers'
export default function Page({ params, searchParams }) {
const cookieStore = cookies()
const token = cookieStore.get('token')
const { slug } = params
return <h1>{slug}</h1>
}
// Next.js 16 - async only
import { cookies } from 'next/headers'
export default async function Page({ params, searchParams }) {
const cookieStore = await cookies()
const token = cookieStore.get('token')
const { slug } = await params
const query = await searchParams
return <h1>{slug}</h1>
}
The typing story is better than most teams realise. Running npx next typegen generates globally available type helpers, PageProps, LayoutProps and RouteContext, which give type-safe access to the async props:
export default async function Page(props: PageProps<'/blog/[slug]'>) {
const { slug } = await props.params
const query = await props.searchParams
return <h1>Blog Post: {slug}</h1>
}
Two async changes are easy to miss because they live in metadata files rather than routes. In opengraph-image, twitter-image, icon and apple-icon, the image generating function now receives params and id as promises, while generateImageMetadata continues to receive synchronous params. The sitemap generating function likewise receives id as a promise, so generateSitemaps returning { id: 0 } now needs Number(await id) downstream rather than arithmetic on a raw number.
Turbopack by default, and the build that fails on purpose
Turbopack is stable and default for next dev and next build. You can delete the --turbopack flags from your package.json scripts.
The trap: if your project has a custom webpack configuration and you run next build, the build fails. That is deliberate, to prevent silent misconfiguration. Vercel's guide is direct about it, and adds a detail worth internalising: if you see a failing build complaining about a webpack configuration you did not write, a plugin is probably adding one.
Three ways out:
{
"scripts": {
"dev": "next dev",
"build": "next build --webpack",
"start": "next start"
}
}
That keeps webpack for production while dev runs on Turbopack. The alternatives are running next build --turbopack to ignore the webpack config, or porting the config to Turbopack options properly. The honest recommendation is the middle path first and the migration second, because a bundler swap and a framework major in one pull request is how upgrades get reverted.
Turbopack configuration also moved out of experimental to the top level:
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
turbopack: {
// options
},
}
export default nextConfig
Two Turbopack behaviours bite existing codebases. Sass imports from node_modules no longer accept the legacy tilde prefix, so @import '~bootstrap/dist/css/bootstrap.min.css' becomes @import 'bootstrap/dist/css/bootstrap.min.css'. And client code importing Node.js native modules produces Module not found: Can't resolve 'fs' rather than being silenced by resolve.fallback. Turbopack offers turbopack.resolveAlias as an equivalent escape hatch, but Vercel's own guidance is to fix the import rather than alias it away. That is the right call. An alias hides a client bundle reaching for server code, and that is worth knowing about.
Cache Components, and why caching is now opt-in
This is the conceptual change, not a mechanical one. Cache Components centre on the "use cache" directive and replace the implicit caching of earlier App Router versions.
Tim Neutkens, co-author and tech lead for Next.js, and his colleagues on the Next.js team stated the reasoning in the Next.js 16 release post: "Unlike the implicit caching found in previous versions of the App Router, caching with Cache Components is entirely opt-in. All dynamic code in any page, layout, or API route is executed at request time by default, giving Next.js an out-of-the-box experience that's better aligned with what developers expect from a full-stack application framework."
Read that as a warning as much as a feature. If you were relying on implicit caching without knowing it, your pages now execute at request time by default, and the first thing you will notice after upgrading is a load pattern you did not have before.
const nextConfig = {
cacheComponents: true,
}
export default nextConfig
Cache Components absorb three previously separate flags: experimental.ppr, experimental.dynamicIO and experimental.useCache are all removed or deprecated in favour of cacheComponents. The route-level export const experimental_ppr is gone too.
One warning that deserves more attention than it gets. Vercel's guide says PPR in Next.js 16 works differently than in Next.js 15 canaries, and that if you are using PPR today you should stay on the Next.js 15 canary you are on. If you adopted PPR early, you are not on the standard upgrade path, and treating this as a routine bump will cost you.
The caching API changes that produce type errors
revalidateTag() now requires a cacheLife profile as its second argument. The single-argument form is deprecated and produces a TypeScript error.
// Before
revalidateTag('posts')
// After - stale-while-revalidate
revalidateTag('posts', 'max')
// Or a built-in profile
revalidateTag('news-feed', 'hours')
// Or an inline expiry
revalidateTag('products', { expire: 3600 })
Two new Server Actions-only APIs sit alongside it, and the distinction between them matters more than the syntax:
'use server'
import { updateTag, refresh } from 'next/cache'
// read-your-writes: user sees their own change immediately
export async function updateUserProfile(userId: string, profile: Profile) {
await db.users.update(userId, profile)
updateTag(`user-${userId}`)
}
// refresh uncached data only, cache untouched
export async function markNotificationAsRead(id: string) {
await db.notifications.markAsRead(id)
refresh()
}
Use revalidateTag with a profile where eventual consistency is acceptable, such as blog posts or product catalogues. Use updateTag in Server Actions where the user must see their own write immediately, such as forms and settings. Use refresh when the data was never cached. Mapping those three onto your existing revalidateTag calls is the one part of this migration that needs a human, because the old single-argument call site does not record which semantic you meant.
Also drop the unstable_ aliases, since cacheLife and cacheTag are now stable:
// Before
import {
unstable_cacheLife as cacheLife,
unstable_cacheTag as cacheTag,
} from 'next/cache'
// After
import { cacheLife, cacheTag } from 'next/cache'
middleware.ts becomes proxy.ts, and loses the Edge runtime
mv middleware.ts proxy.ts
Rename the exported function to proxy as well, even if you use a default export. Config flags carrying the old name change too: skipMiddlewareUrlNormalize becomes skipProxyUrlNormalize. The v16 codemod handles both.
export function proxy(request: Request) {}
The constraint that decides whether this is a rename or a redesign: the Edge runtime is not supported in proxy. The proxy runtime is Node.js and cannot be configured. If you depend on Edge middleware, middleware.ts still exists but is deprecated and will be removed, and Vercel says it will follow up with Edge runtime instructions in a minor release. Teams running geo-routing or auth checks at the edge should treat this as an open item rather than a solved one.
The next/image defaults nobody reads until something looks wrong
Four defaults changed, and none of them fail a build. They change output quietly.
| Setting | Next.js 15 | Next.js 16 | Why it matters |
|---|---|---|---|
images.minimumCacheTTL |
60 seconds | 4 hours (14400s) | Cuts revalidation cost on images lacking cache-control |
images.imageSizes |
Included 16 |
16 removed |
Smaller srcset; 16px was rare because DPR 2 fetches 32px |
images.qualities |
All values 1 to 100 | [75] only |
A quality={80} prop is coerced to 75 |
images.maximumRedirects |
Unlimited | 3 | Set 0 to disable |
images.dangerouslyAllowLocalIP |
Allowed | Blocked by default | Set true only on private networks |
The qualities change is the one that generates support tickets. If your team standardised on quality={90} for hero images, every one of them is now silently served at 75. Restore it explicitly if you need it:
const nextConfig = {
images: {
qualities: [50, 75, 100],
minimumCacheTTL: 60,
},
}
Local image sources with query strings now require images.localPatterns.search configuration, which Vercel added to prevent enumeration attacks. images.domains is deprecated in favour of images.remotePatterns, and next/legacy/image is deprecated in favour of next/image.
Removals and quiet behaviour changes
next lint is removed, and next build no longer runs linting. If your CI relied on the build catching lint errors, it now does not. Migrate with npx @next/codemod@canary next-lint-to-eslint-cli . and add the ESLint step back to CI explicitly.
serverRuntimeConfig and publicRuntimeConfig are removed in favour of environment variables. For values that must be read at runtime rather than bundled at build time, call connection() first:
import { connection } from 'next/server'
export default async function Page() {
await connection()
const config = process.env.RUNTIME_CONFIG
return <p>{config}</p>
}
AMP is gone entirely, including useAmp and export const config = { amp: true }.
Three behaviour changes worth flagging to a platform team. next dev now outputs to .next/dev, separate from next build, so dev and build run concurrently, and a lockfile prevents two instances on one project. The size and First Load JS metrics are removed from next build output, because Vercel found them inaccurate in server-driven architectures using React Server Components. And during next dev, checking whether process.argv includes 'dev' inside your config file now returns false, because the config is no longer loaded twice. If a plugin triggers side effects on that check, it silently stops firing. Check process.env.NODE_ENV === 'development' instead.
That last one is the sort of thing that does not appear in any error message. It just stops working.
India-specific considerations
For teams in India shipping to a global audience, the images.minimumCacheTTL move from 60 seconds to 4 hours is the change with the clearest commercial upside. Image revalidation every 60 seconds on origin images lacking a cache-control header was a real cost line, and Vercel names increased CPU usage and cost as the reason for the change. Teams billing cloud spend in dollars while earning in rupees feel that arithmetic more sharply than most, and this is a default that now works in your favour without any code change. Our cloud FinOps guidance for Indian teams covers the wider pattern.
The Node.js 20.9 floor is the item to check first if you deploy to a managed Indian hosting platform or an older enterprise CI image, since Node.js 18 is no longer supported at all. Verify the runtime before you plan the sprint, not during it.
What we would sequence, and in what order
The order that survives contact with a real codebase:
Upgrade Node to 20.9 or later and TypeScript to 5.1 or later first, on its own branch, and merge it. Run the v16 codemod next and read every comment it leaves rather than trusting the diff. Add default.js to every parallel route slot before you attempt a build, because that failure produces a confusing message. Decide the bundler question deliberately: ship with next build --webpack if you have a custom config, and schedule the Turbopack migration separately. Rename middleware.ts to proxy.ts only after confirming you are not depending on the Edge runtime. Leave cacheComponents switched off for the initial upgrade. It is a programming model change, not a version bump, and mixing it into the same release makes any regression impossible to attribute.
The real cost here is not the codemod. It is the revalidateTag call sites where you have to remember what you meant.
FAQ
How eCorpIT can help
eCorpIT's senior engineering teams handle framework majors like this one for clients who cannot afford a stalled upgrade, and the work is mostly sequencing rather than heroics. We run the codemods, then audit what they left behind, which on Next.js 16 means the revalidateTag call sites and the Edge middleware assumptions that no tool can decide for you. We keep the bundler migration and the Cache Components adoption as separate releases from the version bump so regressions stay attributable. Talk to us if a Next.js upgrade has been sitting in your backlog because nobody wants to own the blast radius. Related work is covered in our custom web application development service and our QA and test automation service.
References
- Next.js 16 - Jimmy Lai, Josh Story, Sebastian Markbage and Tim Neutkens, Vercel, 21 October 2025
- How to upgrade to version 16 - Next.js documentation, version 16.2.10, last updated 13 May 2026
- Upgrading: Codemods - Next.js documentation
- Dynamic APIs are Asynchronous - Next.js documentation
- Getting Started: Upgrading - Next.js documentation
- Next.js 16 (beta) - Vercel
- Next.js 16.1 - Vercel
- Next.js 15 - Vercel
- Tim Neutkens, Co-Author of Next.js on the State of Next - This Dot Labs
- Releases: vercel/next.js - GitHub
- Next.js versions and support - endoflife.date
Last updated: 17 July 2026. Version numbers and configuration defaults verified against the official Next.js documentation on 17 July 2026.