Next.js 16 is the release where the framework finally admits that its caching was confusing, and does something about it.
That is the headline. Turbopack going stable and middleware.ts being renamed are the two changes you will feel first, but the caching model is the one that changes how you write code.
We run this site on Next 16, so what follows is what actually mattered in practice rather than a restatement of the release notes.
Cache Components: the important one
For several versions, Next.js caching was implicit. A fetch might be cached or not depending on the options you passed, whether you had used a dynamic API, and which version you were on. The defaults changed between releases. It was the single most common source of "why is this page stale in production but fine locally".
Next 16's answer is to make it explicit. You mark what should be cached with a directive:
async function getArticles(topic: string) {
'use cache'
const res = await fetch(`https://api.example.com/articles?topic=${topic}`)
return res.json()
}Anything not marked is dynamic. That is the whole mental model, and it is a large improvement: instead of memorising which defaults apply where, you read the code and the answer is written in it.
It pairs with Partial Prerendering, which lets one page be partly static and partly dynamic. The static shell — layout, navigation, headings — is served instantly from the edge, while the personalised parts stream in. Previously a single dynamic call anywhere on a page made the whole page dynamic, which is why so many otherwise-static marketing sites rendered on every request.
Should you adopt it immediately? If you are starting a project, yes. If you have an existing app that works, you can upgrade to 16 without adopting Cache Components and migrate deliberately afterwards.
Turbopack is now the default
Turbopack is stable and is the default bundler for both next dev and next build. Vercel reports Fast Refresh 5–10× faster and builds up to 5× quicker.
In everyday use the dev-server difference is the one you notice — the gap between saving a file and seeing the change is where a working day is quietly spent. Cold starts on a large app improve substantially.
Two practical caveats:
- Custom webpack config does not carry over. If you have a bespoke webpack setup, that is your upgrade work. Most apps do not.
- Some ecosystem plugins lag. Check anything unusual in your build pipeline before assuming it works.
middleware.ts is now proxy.ts
A rename, and a clarifying one. The file was almost universally used for authentication, which led people to assume it ran per-request in front of their application logic like Express middleware. It does not — it runs as a proxy at the network edge, before the cache.
That distinction has always mattered and the old name actively obscured it. Code inside it runs before caching, so it must be fast, it does not have access to your full runtime, and putting a database call there is a mistake the name encouraged.
Migration is mechanical: rename the file, keep the exported function and matcher config. Expect fifteen minutes, not an afternoon.
What actually breaks on upgrade
From doing it, in rough order of how much time each costs:
1. Async params and searchParams. These became Promises in Next 15 and the codemod does not catch every case, particularly in generateMetadata:
export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params
}2. Node version. Next 16 requires a current Node. Check your deploy target before your local machine tells you everything is fine.
3. The webpack config, if you have one. See above.
4. Dependency peer ranges. React 19.2 and anything that pins a React version. This is the usual source of an install that resolves two copies of something.
⚠ Keep the framework version identical across a monorepo. We learned this expensively: the root package declared^16.3.1while every site declared^16.2.9, which resolved two copies of Next in one tree. The symptom was not a version warning — it was a type error insidenext.config.ts, because two structurally differentNextConfigtypes existed at once. If you run a workspace, align the ranges and verify withnode -e "console.log(require('next/package.json').version)"from inside each package.
Should you upgrade?
Yes, fairly promptly, if:
- You are starting something new. Start on 16.
- Your build times or dev feedback loop hurt. Turbopack alone justifies it.
- You are fighting caching bugs. This release is the fix.
- You are on 15 and up to date with your dependencies. The gap is small.
Wait, if:
- You have heavy custom webpack configuration and no time budget for it.
- You depend on a build-pipeline plugin without Turbopack support.
- You are two or more major versions behind. Go 14 → 15 → 16, testing between. Skipping majors is where upgrades turn into weeks.
- You are in a freeze. This is not a security release.
A practical upgrade checklist
- Branch. Obviously, but the number of people who upgrade on main is not zero.
- Node first. Update it locally and on your deploy target, and check the target actually supports it.
- Run the codemods.
npx @next/codemod@canary upgrade latesthandles most of the mechanical work. - Rename
middleware.ts→proxy.ts. - Type-check before running anything.
tsc --noEmitfinds the asyncparamscases faster than clicking around will. - Build, do not just dev. Production builds catch things dev does not — prerendering errors especially.
- Walk the real routes. Dynamic routes, metadata, sitemap, robots, images, forms.
- Then, separately, adopt Cache Components. Not in the same pull request. Upgrade and adopt are two changes and they fail differently.
What this means for performance
The practical gain is not raw rendering speed, it is how much of your page can be static.
Partial Prerendering means one personalised element no longer forces the entire page to render per request. A product page whose only dynamic part is a stock indicator can ship its shell instantly and stream the rest — which shows up directly in LCP, and therefore in Core Web Vitals.
For a content site the effect can be large. For an app that is dynamic end to end, it is modest. Measure before you promise anyone a number, and this is the kind of work we do if you would rather hand it over.
If you are still on Pages Router
Pages Router still works and is still supported. But every feature discussed here — Cache Components, Partial Prerendering, Server Components — is App Router only, and the gap widens with each release.
You do not have to migrate all at once; the two routers coexist in one application. Move new routes to App Router, migrate the rest as you touch them, and let it happen over a year rather than a quarter.
Adopting Cache Components without breaking things
Once you are on 16 and stable, this is the part worth doing properly. The mistake is to treat it as a find-and-replace.
Start at the leaves, not the pages. Mark the individual data functions — the one that fetches articles, the one that reads settings — rather than whole route segments. A wrongly cached page serves stale content to everybody; a wrongly cached function is easier to reason about and to revert.
Know what a cached function may close over. Anything inside 'use cache' must not depend on request-specific state — cookies, headers, the current user. If it does, you will serve one visitor's data to another, and it will work perfectly in development where there is one visitor. This is the failure mode to be genuinely careful about.
Set lifetimes deliberately. A cached function with no expiry is a permanent cache. Decide how stale each thing may be — a product listing, five minutes; a legal page, a day; a navigation menu, until it changes — and say so.
Have an invalidation path before you cache anything. Tag your caches and revalidate them when the underlying data changes. Caching without invalidation is how a CMS edit takes an hour to appear and somebody redeploys to fix it.
Verify with a production build. next dev does not cache the way production does. A caching change that you have only seen in development has not been tested.
The order that works: upgrade, ship, wait a week, then adopt caching one function at a time with a way to measure whether each one helped. Doing both at once means that when something is stale you will not know which change caused it.
What did not change, and is worth remembering
Upgrade posts create an impression that everything moved. Most of it did not, and knowing that shortens the work.
Your routing is untouched. File-based routes, dynamic segments, layouts, loading and error boundaries — all identical. If you were fluent in App Router on 15, you are fluent on 16.
Server Components and Server Actions are unchanged. Cache Components sits alongside them, not on top of them. Everything in React Server Components applies exactly as before.
Images, fonts and metadata are the same APIs. next/image, next/font, generateMetadata, sitemap.ts, robots.ts — no changes, other than robots.ts still needing to sit at the root of app/ rather than inside a route group, which has caught people since long before this release.
Deployment is unchanged. Whatever built and served your app on 15 builds and serves it on 16.
So the honest scope of this upgrade is: rename one file, fix any async params the codemod missed, check your Node version, and — only if you have one — deal with a custom webpack config. That is genuinely most upgrades. The large-sounding change, Cache Components, is opt-in and can wait until the upgrade itself has been stable for a week.
Frequently asked questions
What is the biggest change in Next.js 16? Cache Components. Caching is now explicit — you mark what should be cached with a 'use cache' directive, and everything else is dynamic. It replaces a model where the defaults were implicit and changed between versions.
Is Turbopack production-ready? Yes, it is stable and the default for both dev and build in Next 16. The main risk is a custom webpack configuration, which does not carry over.
Do I have to rename middleware.ts? Yes, to proxy.ts. It is a mechanical rename — the exported function and matcher config stay the same. The new name reflects that the code runs as an edge proxy before the cache, not as application middleware.
Will Next.js 16 break my app? The common breakages are async params/searchParams, the Node version requirement, and custom webpack config. Run the official codemods, type-check, and do a production build rather than only next dev.
Should I upgrade from 14 straight to 16? Go through 15, testing in between. Skipping a major version means debugging two sets of breaking changes simultaneously, and it is the difference between an afternoon and a fortnight.
Is App Router required for Next.js 16? No, Pages Router is still supported. But Cache Components, Partial Prerendering and Server Components are App Router only, so staying on Pages means opting out of most of what the release offers.

.webp&w=128&q=75)
.webp&w=256&q=75)