Next.js 16 Migration Guide: Fixing the Silent Breakages
In short
Four changes in Next.js 16 can break a production app without throwing a single error: the middleware.ts to proxy.ts rename, the deprecated single-argument revalidateTag, caching flipping to opt-in, and async params. I migrated my production reputation SaaS to 16.2 in about 14.5 hours spread over three days, and every problem I hit was a correctness bug, not a crash. This post is the checklist the official upgrade guide (last updated May 13, 2026) does not give you: the symptom, the root cause, the exact fix, and how you would actually notice each one in production.

On this page
- What breaks silently when you upgrade to Next.js 16?
- Why is middleware.ts silently ignored?
- Why does revalidateTag serve stale data now?
- Why did my static pages suddenly go dynamic?
- What changed about params and searchParams?
- 'use cache' vs ISR vs fetch caching: which one should you use?
- What breaks when Turbopack becomes the default build?
- Which changes does the codemod handle, and which are manual?
- How long does a Next.js 16 migration take on a production app?
- Should you wait for Next.js 16.3?
- Key takeaways
What breaks silently when you upgrade to Next.js 16?
Four changes ship with no build error and no runtime exception: middleware.ts is ignored until you rename it to proxy.ts, single-argument revalidateTag serves stale data, caching is now opt-in so formerly static routes go dynamic, and params became a Promise so property access returns undefined in plain JavaScript. Everything compiles. Everything deploys. The bugs show up as wrong behavior, not red text.
Version context first, because it matters for what you are upgrading into: Next.js 16 went GA on October 21, 2025, 16.2 shipped on March 18, 2026 with roughly 400% faster dev startup, and 16.3 has been in canary since June 4, 2026.
Here is the table I wish I had before I started:
| Change | Symptom | Fix |
middleware.ts renamed to proxy.ts | Auth redirects, rewrites, and header rules silently stop running | Rename the file to proxy.ts, export proxy, delete runtime: 'edge' |
Single-arg revalidateTag(tag) deprecated | Mutations save, but the next request still shows stale data | updateTag(tag) in Server Actions, or revalidateTag(tag, 'max') |
| Caching flipped to opt-in | Formerly static/ISR routes render on every request; TTFB and DB load climb | Add 'use cache' with cacheLife/cacheTag to routes that should cache |
params/searchParams are async | params.id is undefined; queries return empty results or 404s | await params (the codemod rewrites most call sites) |
| Data freshness need | Typical route | Use |
| Changes only on deploy | Marketing pages, docs | 'use cache' with default cacheLife |
| Refresh on a schedule (minutes to hours) | Public listings, blog, review widgets | 'use cache' + cacheLife('hours') (the ISR replacement) |
| Refresh after user mutations | Authed pages mutated by Server Actions | 'use cache' + cacheTag + updateTag |
| Caching someone else's API response | Any route calling third-party APIs | fetch(url, { next: { revalidate: 300 } }) |
| Always fresh, per-user | Admin panels, billing, anything with PII | No cache, stay dynamic |
The mental shift: in 15 you fought the framework to make things dynamic; in 16 you declare what is cacheable and everything else is dynamic by default. My rule on the SaaS was simple. Public, shared, tolerant of minutes of staleness: cache it. Authenticated or money-related: do not.

What breaks when Turbopack becomes the default build?
Turbopack is the default for both dev and production builds in Next.js 16, and your webpack() config in next.config is ignored. The tell is this exact line in your build output: Webpack is configured while Turbopack is not, which may cause problems. Anything that depended on a custom loader breaks next, usually SVGR, with Module not found: Can't resolve errors or SVGs importing as URLs instead of components.
The fix is to port loaders to turbopack.rules:
// next.config.ts
const nextConfig = {
turbopack: {
rules: {
'*.svg': {
loaders: ['@svgr/webpack'],
as: '*.js',
},
},
},
}
export default nextConfig
If a plugin has no Turbopack equivalent yet, next build --webpack is the escape hatch, but treat it as temporary. The payoff is real: on my machine, dev cold start dropped from about 7.5s to under 2s after 16.2, and the production build went from 2m40s to a bit over a minute. That alone justified the migration for me.
Which changes does the codemod handle, and which are manual?
Run npx @next/codemod@canary upgrade latest first; it reliably handles the syntactic changes and none of the semantic ones. On my codebase it caught every async params/searchParams rewrite in TypeScript files, the import renames, and the config key moves. That is the easy half.
What it cannot do for you:
- Decide what should be cached. The opt-in caching audit is judgment work, route by route.
- Fix revalidateTag semantics. It will not choose between
updateTagandrevalidateTag(tag, 'max')because that depends on your UX. - Validate your proxy.ts logic. The rename is trivial; confirming your matchers and auth still behave is not.
- Port webpack loaders.
turbopack.rulesneeds a human who knows why each loader existed.
A loud failure costs you an afternoon. A silent failure costs you a week, because you spend the first six days not knowing it exists.
How long does a Next.js 16 migration take on a production app?
About 14.5 hours for my reputation SaaS: roughly 38 routes, 11 Server Actions, two cron jobs, one developer. Here is the actual breakdown, so you can budget against your own route count:
- Read the upgrade guide, audit dependencies for Turbopack support: 1.5 h
- Run the codemod, review the diff: 1 h
- Fix async params leftovers in plain JS utility files the codemod missed: 2 h
- Rename middleware.ts to proxy.ts, remove edge runtime assumptions: 1 h
- Sweep every revalidateTag call, convert to updateTag where actions need fresh reads: 1.5 h
- Route-by-route cache audit, adding 'use cache', cacheLife, and cacheTag: 4 h
- Turbopack build fixes (SVGR, one Sass quirk): 1.5 h
- Staging deploy with route-by-route verification of cache behavior and auth: 2 h
Step 6 dominates, and it scales with route count, not codebase size. The verification pass in step 8 reuses the same discipline I described in my audit checklist for fixing vibe-coded apps ↗: open every route, check headers, confirm the cache state you expect. A good chunk of my app rescue and optimization ↗ work this spring has been exactly this, untangling Next.js 16 upgrades where someone shipped the codemod output straight to production and only noticed when customers reported stale dashboards.
Should you wait for Next.js 16.3?
No. Migrate to 16.2 now; it shipped on March 18, 2026, has had almost three months of patch releases, and the dev startup improvement is worth the effort by itself. 16.3 has been in canary since June 4, 2026 and is incremental, not corrective. There is no breaking change on the horizon that would invalidate work you do today.
My verdict after doing it on a revenue-generating app: Next.js 16 is worth migrating to for the dev-server speed alone, but do it behind a staging deploy with route-by-route cache verification, because the failures are correctness bugs, not crashes. Nothing will page you. Your error tracker stays green while users see stale data and your middleware does nothing. Every greenfield build in my MVP development ↗ work starts on 16.2 now, and it is the default across my web development ↗ client projects, but I will not upgrade an existing production app without that verification pass.
Key takeaways
- Four Next.js 16 changes break apps with zero errors: the proxy.ts rename, deprecated single-arg revalidateTag, opt-in caching, and async params.
- middleware.ts is silently ignored: rename it to proxy.ts, and note it runs on the Node.js runtime only since edge support was removed.
- Caching is now opt-in: routes that were static in 15 go dynamic in 16, so watch TTFB and database load, not error logs.
- The codemod handles syntax, not semantics: async params get rewritten for you; cache decisions and revalidation behavior do not.
- Budget by route count: my 38-route production SaaS took about 14.5 hours, and the route-by-route cache audit was the largest single step at 4 hours.
FAQ
Why is my middleware.ts not running in Next.js 16?
Because Next.js 16 renamed it. middleware.ts is silently ignored; the file must be called proxy.ts and export a `proxy` function, and it runs on the Node.js runtime only since edge support was removed. There is no error, so auth redirects and rewrites just stop. Rename the file and remove any `runtime: 'edge'` config.
Is revalidateTag deprecated?
The single-argument form is. In Next.js 16, `revalidateTag('tag')` behaves as stale-while-revalidate, so the next request still serves old data. Use `updateTag('tag')` inside Server Actions when the user must immediately see their own change, or `revalidateTag('tag', 'max')` for an immediate expire from webhooks and route handlers.
Can I still use webpack with Next.js 16?
Yes, as an escape hatch. Turbopack is the default for dev and production builds, and your `webpack()` config is ignored with a warning. Run `next build --webpack` to opt back in temporarily, but plan to port custom loaders to `turbopack.rules`, since the webpack path is clearly in maintenance mode.
Do I have to rewrite ISR routes to 'use cache'?
Not immediately. The classic `revalidate` export still works in Next.js 16, so existing ISR routes keep functioning. The forward path is `'use cache'` with `cacheLife`, which gives the same scheduled-refresh behavior with finer control. I migrated mine during the cache audit, but it is the one change you can safely defer.
Working on something like this?
I build web apps, AI features, and mobile products for clients. If this article matches a problem you have, tell me about it.
Start a conversationMalik Hamza Shabbir · Full-Stack & AI Engineer
I build full-stack and AI products solo: a reputation SaaS in production, RAG pipelines, and React Native apps. I write from what I ship, not from documentation summaries.
Related articles
How to Fix a Vibe-Coded App: My Rescue Audit Checklist
Roughly 8,000 of 10,000 AI-built startup apps need rescue work in 2026. Here is the 60-minute triage and fix order I use to save them without a rewrite.
GEO in 2026: Getting Cited by ChatGPT and Perplexity
GEO means writing answer-first chunks AI engines can lift: 69% of Google searches are zero-click in 2026, but ChatGPT referrals convert at 15.9%.
Leaving Vercel in 2026: The Real Self-Hosting Cost Math
Self-hosting Next.js on Hetzner with Coolify costs me $6-17/mo vs $40-150 on Vercel Pro. The real math, ops hours included, after the April 2026 breach.