Skip to main content
Guide

YourCodemodPassed.YourNext.js16BuildDidNot.

The Next.js 16 codemod does its job and the migration still fails, because the two changes that cost you a week are architectural. Where your auth boundary lives, and who put a webpack config in your project.

An engineer reviewing a Next.js 16 upgrade branch, comparing proxy runtime behaviour against the previous edge middleware.
|Aug 22, 2026|Next.jsMigrationApp RouterTurbopackPlatform Engineering

Introduction

You ran the upgrade codemod on a branch. It did exactly what it said. It moved your Turbopack settings to the top level, renamed your middleware file to proxy, and stripped the old unstable prefixes. Then the build failed on a webpack configuration nobody on your team remembers writing, and sign-in stopped working in preview.

So now you are explaining to someone why the one-day job is not done. That conversation is the reason this page exists.

The change list itself is not the problem. Vercel publishes a complete upgrade guide and it is genuinely good. Everything factual below is checked against it. We are not going to retype it, because the reader who wants the change list is already served. This page is for the person who has to work out which of those changes will hurt their app, in what order, and whether to do it at all this quarter.

ā— QUICK ANSWER

What actually breaks in a Next.js 16 migration? Two things carry most of the cost. The middleware file is renamed to proxy, and proxy runs on the Node runtime only, so edge auth is a design decision rather than a rename. And Turbopack is now the default builder, so a custom webpack config makes next build fail on purpose. Everything else is codemods and careful review.

Should You Upgrade to Next.js 16 At All?

Upgrade if something is pushing you. Do not upgrade because a version number went up.

The real reasons look like this. A dependency you need has dropped support for 15. Your platform team sent a Node 18 end-of-life notice and Next.js 16 requires Node 20.9 or newer anyway. A security review asked why you are two majors behind. A new hire spent their first week fighting a toolchain nobody else wants to defend. Each of those is a forcing function with a date attached, and dates are what get migration work scheduled instead of admired.

Check the floor before anything else, because it decides whether this is one project or two. Next.js 16 needs Node 20.9 as a minimum and no longer supports Node 18. TypeScript has to be 5.1 or above. Browser support moves to Chrome, Edge and Firefox 111 and up, plus Safari 16.4 and up. If your deployment images are still on Node 18, that is a separate piece of work with its own risk, and stacking it into the same pull request is how a two-day job becomes a fortnight of bisecting.

One group has an explicit reason to wait, and it comes from the Next.js team rather than from us. If you are running Partial Prerendering today, PPR in 16 does not work the way it did in the 15 canaries. The documented advice is to stay on the canary you are on until you are ready to adopt the Cache Components model properly.

What Actually Breaks When You Upgrade?

It helps to stop reading the release notes as a list and start sorting them by how they will reach you. There are four groups, and only one of them is comfortable.

Fails loudly at build time. A custom webpack config now stops the build. Parallel route slots without an explicit default file now stop the build. These are the good ones. You find out immediately, on a branch, before anyone else is affected.

Fails at compile time. Synchronous access to cookies, headers, draftMode, params and searchParams is gone. Calling revalidateTag with one argument is deprecated and produces a type error. Painful in volume, but your tooling points at every site.

Changes behaviour without telling you. The image defaults move. Cache lifetime, allowed qualities, the size list and the redirect limit all changed. Smooth scrolling behaves differently on navigation. Nothing throws. You ship it, and a week later somebody asks why the product photos look softer.

Gone entirely. AMP support, the next lint command, serverRuntimeConfig and publicRuntimeConfig, three devIndicators options, the dynamicIO and useCache experimental flags, and unstable_rootParams.

Sorting them this way changes what you do first. The loud failures are cheap to find and you should go looking for them deliberately on day one. The silent behaviour changes are the ones that need a human looking at a rendered page, and they are the reason a migration is not finished when the build turns green.

Why Does the Proxy Rename Break Authentication?

Because it is not a rename. It is a runtime change wearing a rename's clothing.

In Next.js 16 the middleware filename is deprecated and replaced by proxy, to make it clearer that the file sits at the network boundary and does routing work. The mechanical part is trivial. Move the file, rename the exported function to proxy, and update any config flag that carried the old name, so skipMiddlewareUrlNormalize becomes skipProxyUrlNormalize. The codemod handles all of it.

Here is the part that matters. The edge runtime is not supported in proxy. The proxy runtime is Node, and it cannot be configured to be anything else. If you need the edge runtime, the documented answer today is to keep using middleware, and the Next.js team has said it will follow up with edge instructions in a later minor release.

So the question the codemod cannot answer for you is what your middleware was doing at the edge in the first place. If it was checking a session cookie before a request reached your application, deciding which region to serve, or assigning an A/B bucket, then that work has been running physically close to your user. Move it into proxy and it runs on your Node server instead. The security properties are intact. The latency profile is not the same, and neither is the failure behaviour when your origin is under load.

This is why the auth vendors come up. If you use a hosted authentication provider and its integration is written as edge middleware, you inherit that provider's timeline as well as your own. Check what runtime their current integration targets before you plan the work, not after.

There are three honest options and no single right answer. Move to proxy and accept auth checks running on Node. Keep middleware for now, on purpose, and write down why so the next person does not treat it as an oversight. Or move that responsibility out of the framework entirely, to your CDN or load balancer, which is more work up front and stops this being a Next.js decision at every future major.

Pick deliberately. The failure mode we see is a team taking option one by accident, because a codemod chose it for them.

Why Did Your Build Start Failing on Turbopack?

Turbopack is stable in Next.js 16 and it is the default for both next dev and next build. You no longer pass a flag to opt in, which also means you no longer pass a flag to opt out.

If your project has a custom webpack configuration and you run next build, the build fails. That is deliberate. The alternative would be to silently ignore your configuration and hand you a bundle built under rules you did not choose, which is a worse outcome discovered much later.

Before you start porting anything, check whether the config is even yours. The Next.js documentation raises this directly: if you see the failure and you do not think you define a webpack config, a plugin added one. Bundle analysers, monitoring SDKs, older styling integrations and internal shared configs all do this. We have watched teams spend a day translating loader rules that came from a package they could have simply updated.

You have three ways forward. Build with Turbopack and ignore the webpack config, which is right when the config turns out to be dead weight. Port the config to Turbopack options, which is right when it does real work. Or opt out with the webpack flag on the build while keeping Turbopack for development, which is the sensible holding position when you want the upgrade landed this sprint and the bundler decision taken separately.

Two smaller things bite here. Turbopack does not support the legacy tilde prefix for Sass imports from node_modules, so those import paths need shortening. And if client code imports a module that reaches for a Node builtin, the webpack resolve fallback trick has a Turbopack equivalent in resolveAlias, though the better fix is to stop the client bundle importing it at all.

Why Did cookies() and params Stop Working?

Because the grace period ended and most teams did not notice it was a grace period.

Next.js 15 made the request-time APIs asynchronous. cookies, headers, draftMode, params and searchParams all became things you await. Synchronous access kept working, with a warning in the logs. That warning was the compatibility window. Next.js 16 removes synchronous access completely, so code that only warned in 15 now fails.

If your development logs were noisy in 15 and everyone learned to scroll past them, this is where that habit gets charged to you. The volume is the problem rather than the difficulty. Every dynamic page, layout, route handler and default file that reads params is a site to change.

Run the dedicated codemod for this, because the general upgrade codemod does not cover it. It is next-async-request-api, and it is separate on purpose. Then run next typegen, which generates PageProps, LayoutProps and RouteContext helpers so the type system carries the migration rather than your reviewers.

Two related changes hide in the metadata files and are easy to miss in review. The image generating functions for opengraph-image, twitter-image, icon and apple-icon now receive params and id as promises. The sitemap function receives its id from generateSitemaps as a promise too. Nothing about those files is in your main render path, so nobody looks at them, and a broken social card is the sort of thing you find out about from a customer.

What Changed That No Codemod Will Catch?

This is the list to read slowly, because every item here needs a person.

Parallel routes now require a default file. Every parallel route slot needs an explicit default file or the build fails. Restoring the old behaviour means adding one that either calls notFound or returns null. Which of those you want is a product decision about what the slot should show, not a mechanical fix.

revalidateTag changed shape. It now takes a second argument naming a cache lifetime profile, and the single-argument form produces a type error. There are two new companions worth knowing about. updateTag is for Server Actions and gives read-your-writes behaviour, so a user sees their own change immediately instead of stale data. refresh refreshes the client router from a Server Action. Reaching for revalidateTag everywhere out of habit is how you end up with a form that saves correctly and appears not to.

Runtime config is gone. serverRuntimeConfig and publicRuntimeConfig have been removed in favour of environment variables. If a value has to be read at runtime rather than baked in at build time, call connection() before reading from the environment, otherwise you get a build-time snapshot and a confusing bug in whichever environment you deploy to second.

The image defaults moved, quietly. minimumCacheTTL goes from 60 seconds to 4 hours. The qualities list narrows to 75 only, and any other quality value you pass is coerced to the nearest allowed one. The value 16 is dropped from imageSizes. Image redirects are capped at 3 where they were unlimited. Local image sources with query strings now need an explicit localPatterns entry, and local IP optimisation is blocked unless you turn it back on. Separately, next/legacy/image and the images.domains config are both deprecated, so remotePatterns is where you want to be.

Linting quietly left the building. The next lint command has been removed and next build no longer runs linting at all. If your pipeline depended on the build to catch lint failures, it now passes without checking. That is the change most likely to go unnoticed for months, because the symptom is an absence.

One more that looks cosmetic and is not. Next.js used to override a global smooth scroll behaviour during navigation so route changes felt instant. It no longer does. Add the data-scroll-behavior attribute to your html element to get the old feel back.

Who Should Not Upgrade to Next.js 16 Yet?

Plenty of people reading this should close the tab and do it themselves this afternoon.

If your app is on the App Router, has no custom webpack config, no middleware running at the edge, no parallel routes and no Partial Prerendering, this is a genuinely small job. Run the upgrade codemod, run the async request API codemod, fix what the type checker points at, look at your images on a real page, and ship it. You do not need an agency for that and we would rather tell you now than after a scoping call.

Wait if you are running PPR, because the framework's own guidance is to stay on your current 15 canary until you are ready for Cache Components. Wait if you are inside a launch window or a seasonal peak, because nothing here is urgent enough to spend that risk on. And handle the Node floor first, as its own change, if you are still on Node 18.

The case for help is narrower than most agencies will admit. It is worth it when your middleware does real work at the edge and moving it changes your auth architecture, when the app is large enough that the async request API diff is genuinely unreviewable, or when nobody left on the team wrote the parts that will break.

And to say the quiet part plainly. Nobody needs to rebuild a Next.js 14 app to get to 16. If someone looks at this work and comes back with a rewrite proposal, ask them which specific breaking change requires it. There is not one. A rewrite bills more than a migration, which is exactly why you should hear the recommendation and then check it. If you are genuinely weighing that question for other reasons, our rebuild versus refactor guide works through how to decide honestly.

How Long Does This Migration Actually Take?

Anyone who answers that without seeing your repository is guessing. What we can give you is the list of things that actually move the number, so you can estimate it yourself before you ask anyone.

The drivers, roughly in order of how much they matter:

How Do You Ship It Without Freezing the Roadmap?

The mistake is treating this as one pull request. It is four or five, and sequencing them is most of the skill.

Start with an inventory, not a branch. Before touching anything, count the things above. Search for synchronous request API usage, look for a webpack config and find out who owns it, list your parallel route slots, and check your Node version in every deploy target. An hour here changes the plan more than a day of trial upgrades.

Raise the floor on its own. Node 20.9 and TypeScript 5.1 go in a separate change that ships before the framework move. If something breaks, you want to know which of the two did it.

Take the async request API change as its own reviewable unit. Run the codemod, then run next typegen so types carry the weight. This diff is large and boring, and mixing it with a decision-heavy change is how real problems get approved by a tired reviewer.

Make the proxy call deliberately. Write down which of the three options you chose and why. If you keep middleware on purpose, that comment is the most valuable line in the pull request.

Land the bundler question last, and separately. Get the upgrade in with the webpack flag if you need to, then decide about Turbopack when it is not blocking anything else.

Run both the old and new builds in CI while you cross over. It costs a little pipeline time and it removes the argument about whether the framework or the code caused a failure. We use the same staged approach for larger structural work, and the reasoning is set out in our staged migration guide. If you are also weighing how this app should be structured at scale, our Next.js enterprise architecture guide covers that side.

One honest note about our own position here. This site runs on Next.js 15.5 with the App Router, so we are working through the same upgrade path as you are. We build and ship products as a development partner, which means we do this on our own software before we do it on anyone else's.

What a Migration Risk Read Covers

If you want a second opinion before you commit the time, send us the repository or just describe the setup. A senior engineer reads it against the Next.js 16 change surface and sends back an ordered list of what will break, what is mechanical and what needs a decision.

Most of what comes back, you will be able to do yourselves. We say so where that is true, because a client who upgrades their own app and comes back for the platform work is worth considerably more to Geminate Solutions than one who felt handled. If you would rather talk it through first, our web platform engineering team can walk the sequencing with you on a scoping call.

YK
Written by

CEO and co-founder of Geminate Solutions, a software and product development partner. He has led teams shipping custom web apps, mobile apps, SaaS platforms, and AI products that serve over 250,000 daily active users.

Free 48-hour migration risk read

Find out what your Next.js 16 upgrade will actually cost you in effort.

Send us the repository or describe the setup. A senior engineer reads it against the Next.js 16 change surface and sends back an ordered list within 48 hours, split into mechanical work and decisions. Most of it you can do yourselves, and we say so where that is true.

  • Whether your middleware runs at the edge, and what moving it does to authentication
  • Who actually created the webpack config that will stop your build
  • How many files still read params, cookies or headers synchronously
  • Which image and caching defaults will change behaviour without throwing an error

Get your free Next.js 16 migration risk read

Drop your repository or app URL and work email. We reply within 48 hours.

Reply in 48 hours. Free, no pitch, no commitment. By submitting, you agree we may use your details to reply, under our legitimate interest and stored via EmailJS. We never sell your data. Privacy Policy.

FAQ

Frequently asked questions

Should I upgrade to Next.js 16?
Upgrade if something is pushing you. A dependency that now requires 16, a Node 18 end-of-life notice, or a security review are all real reasons. Curiosity is not, because the work lands on whoever is already carrying the roadmap. The one group the Next.js team tells to wait is anyone running Partial Prerendering today, because PPR in 16 behaves differently from the 15 canaries and the documented advice is to stay on the canary you are on.
What is the difference between proxy and middleware in Next.js 16?
The file is renamed and the runtime is not the same. In Next.js 16 the middleware filename is deprecated in favour of proxy, and proxy runs on the Node runtime only. That runtime cannot be configured. The edge runtime is not supported in proxy at all, so if your middleware ran at the edge, this is not a rename. It moves where that code executes. The documented option for teams that need edge is to keep using middleware for now.
Does the Next.js 16 codemod do the whole upgrade?
No, and the gap is the part that bites. The upgrade codemod moves your Turbopack config, migrates next lint to the ESLint CLI, renames middleware to proxy, strips unstable prefixes and removes the experimental PPR segment config. It does not migrate synchronous request API access. That is a second codemod, next-async-request-api, and you have to know to run it.
Why does next build fail after upgrading to Next.js 16?
Turbopack is the default builder in Next.js 16 for both next dev and next build. If the project has a custom webpack configuration, next build fails on purpose rather than falling back, to stop you shipping a build that quietly ignored your config. You can build with Turbopack anyway, port the config to Turbopack options, or opt out with the webpack flag. Worth checking before you go hunting: if you did not write a webpack config yourself, a plugin almost certainly added one.
Why did cookies() and params stop working in Next.js 16?
Next.js 15 made cookies, headers, draftMode, params and searchParams asynchronous and kept synchronous access working as a temporary compatibility measure that logged a warning. Next.js 16 removes synchronous access completely. If your logs were full of that warning in 15 and nobody acted on it, the warning was a countdown and it has now run out.
What Next.js 16 changes will not show up as an error?
The image defaults are the quiet ones. minimumCacheTTL moves from 60 seconds to 4 hours, the qualities list narrows to 75 with other values coerced to the nearest allowed, 16 is dropped from imageSizes, and image redirects are capped at 3 instead of unlimited. None of that throws. It changes what your users see and what your cache does. The other silent one is linting. next lint is gone and next build no longer lints, so a pipeline that relied on the build to catch lint failures now passes without checking anything.
Do I need to rebuild my Next.js 14 app to get to 16?
No. We have not seen a case where the honest answer to a 14 to 16 upgrade was a rewrite, and you should treat that recommendation with suspicion when it arrives. This is a sequence of contained changes with codemods for the mechanical ones. The parts that need judgement are where your auth boundary sits and who injected a webpack config. Neither is a reason to throw away working software.
Is Next.js 16 stable?
Yes. Turbopack is stable and on by default in 16, and the release has been through several minors. Stability of the framework is not the question that decides your upgrade though. The question is how much of the removed surface your app still uses: synchronous request APIs, edge middleware, a custom webpack config, parallel routes without default files, AMP, or runtime config. Count those first.
FREE WEBSITE REVIEW

Get a free 24-hour review of your website

Send us your website link on WhatsApp. Within 24 hours we tell you exactly what is costing you customers and what we would fix first. No obligation and no sales script.

Send my website for review

4.9 rated Ā· 50+ products shipped Ā· 250K+ daily users served

GET STARTED

Already built something, and it is starting to break?

Most teams that reach us have a working product and a growing list of things that scare them. We read the code first and tell you what actually needs fixing, including the parts that do not. Rebuilding from scratch is rarely the honest answer.

Related Articles