
Migrating to Next.js 16: What Breaks, What Works, and When to Upgrade
Honest migration guide: what broke when we upgraded our agency website to Next.js 16, realistic time estimates, and a decision framework for when to upgrade.
Last week, we migrated Numen Technology to Next.js 16. The official announcement promised 2-5× faster builds with Turbopack, and up to 10× faster Fast Refresh. What they didn't promise was the three hours debugging async API changes, or the moment when all our page params suddenly returned promises instead of values.
Here's what actually happened, what you need to know before upgrading, and a framework for deciding whether to upgrade now or wait.
What Actually Changed (And Why It Matters)
Next.js 16 isn't a minor version bump. It's a fundamental shift in how the framework handles builds, data fetching, and caching. Released on 21 October 2025, it marks Turbopack's transition from experimental to default bundler, removes synchronous access to request APIs entirely, and introduces a new caching model via Cache Components.
The changes break things. That's not hyperbole—our build succeeded but threw runtime errors until we properly migrated every async API call. Other teams reported worse: custom Webpack configurations ignored entirely, SVG imports returning undefined in production, third-party packages incompatible.
But here's why it matters despite the pain: faster builds don't just save developer time—they change what's possible. When you can iterate in 5 seconds instead of 15, you ship more, test more, learn more. When Fast Refresh is instant, you stay in flow instead of context-switching. These aren't marginal improvements. They're the difference between shipping cautiously and shipping confidently.
Key Takeaways
What you'll learn:
- The three breaking changes that affect almost every Next.js app
- Real migration time estimates (not marketing promises)
- A decision framework for when to upgrade versus when to wait
- Practical steps for migrating async request APIs
- How build speed improvements translate to business outcomes
Reading time: 15 minutes | Migration difficulty: Medium to high | Our migration time: 4 hours for a medium-sized agency site
The Three Changes That Break Most Apps
1. Async Request APIs (Affects ~95% of Apps)
The biggest breaking change: params, searchParams, cookies(), headers(), and draftMode() now return promises. Synchronous access is completely removed.
What breaks:
// This worked in Next.js 15
export default function Page({ params, searchParams }) {
const slug = params.slug; // Direct access
const query = searchParams.q; // Direct access
const cookieStore = cookies(); // Synchronous
// ...
}
What you need now:
// Required in Next.js 16
export default async function Page({ params, searchParams }) {
const { slug } = await params; // Must await
const { q } = await searchParams; // Must await
const cookieStore = await cookies(); // Must await
// ...
}
Every route that uses dynamic data needs updating. Our agency site had 12 dynamic routes—that's 12 files needing changes, plus any shared components accessing these APIs.
The automated codemod handles about 80% of these cases: npx @next/codemod@canary upgrade latest. But complex patterns—nested components, conditional access, custom hooks—require manual fixes.
Why this change matters: It's not just ceremony. Making these APIs explicitly async clarifies your application's data flow. You're forced to think about where async boundaries exist, which makes architectures clearer and bugs more obvious. As I wrote about in systems thinking, forcing explicit boundaries in complex systems reduces emergent bugs.
2. Turbopack Becomes Default (May Break Custom Configs)
Turbopack is now the default bundler for all new projects. It's faster—legitimately faster. Our builds dropped from 11.3 seconds to 4.2 seconds. That's 2.7× faster, and it's noticeable every single time.
But here's the catch: Turbopack ignores custom Webpack configurations entirely. If you have webpack() customisation in next.config.js, Next.js 16 silently skips it when using Turbopack.
What breaks:
- Custom loaders (SVG, CSS modules with specific configs)
- Webpack plugins (bundle analysis, compression, etc.)
- Module aliases beyond Next.js defaults
- Any build-time transformations relying on Webpack
What we had to change:
We used a custom SVG loader in Next.js 15. It broke immediately. SVG imports returned undefined in production whilst working fine in development—the worst kind of bug.
Solution: Switched to Next.js's built-in SVG handling and inlined critical icons. Took 90 minutes to refactor, but the code is cleaner now and doesn't rely on custom build configuration.
When to use Webpack instead: You can opt out by setting turbo: false in next.config.js. Do this if you have complex Webpack customisation that's critical and can't be easily replaced. But know that you're sacrificing significant speed improvements.
3. Caching API Changes (Affects Cache Invalidation)
If you're using revalidateTag() for cache invalidation, it now requires a second cacheLife profile argument:
// Next.js 15
revalidateTag('posts');
// Next.js 16
revalidateTag('posts', 'max'); // Second argument required
Additionally, two new APIs:
updateTag()- Server Actions only, provides read-your-writes semanticsrefresh()- Server Actions only, refreshes uncached data
We didn't use revalidateTag() extensively, so this was a quick fix. But if you're doing sophisticated caching with on-demand revalidation, budget time to understand the new cache profiles and decide between revalidateTag() and updateTag().
Real Build Performance: Not Just Marketing
The official numbers are 2-5× faster builds and up to 10× faster Fast Refresh. Here's what we actually measured on numentechnology.co.uk:
Production Builds
Next.js 15 with Webpack:
- Initial build: 11.3 seconds
- Incremental rebuild: 8.7 seconds
Next.js 16 with Turbopack:
- Initial build: 4.2 seconds
- Incremental rebuild: 2.9 seconds
That's 2.7× faster for full builds and 3× faster for incremental builds. These aren't synthetic benchmarks—this is our real agency website with about 15 routes, dynamic data fetching, and optimised images.
Development Experience
Fast Refresh went from "noticeable lag" to "instant." I can't quantify it precisely because it's so fast now that measuring feels pointless. You change a component, you see the update. No coffee break, no context switch.
This matters more than the numbers suggest. When feedback is instant, you experiment more. You try things. You refactor with confidence because you see results immediately. This is what changes productivity—not saving 7 seconds per build, but removing the friction that makes you hesitate to iterate.
What This Means for Your Work
If you're shipping features daily, these speed improvements compound. Assume 50 builds per day across your team (conservative for active development):
- Old builds: 50 × 11s = 550 seconds = 9 minutes daily
- New builds: 50 × 4s = 200 seconds = 3.3 minutes daily
- Saved: 5.7 minutes per day = 28.5 minutes per week = 2 hours per month
That's two hours your team isn't waiting for builds. But the real value isn't the saved time—it's what you do with faster feedback loops. As I explored in pattern matching and decision making, faster feedback makes your mental patterns more accurate because you iterate more and learn faster.
Migration Path: What We Actually Did
Here's the honest timeline for migrating numentechnology.co.uk:
Hour 1: Dependencies and Upgrade
Time: 20 minutes
# Update to latest versions
npm install next@latest react@latest react-dom@latest
# Run automated codemod
npx @next/codemod@canary upgrade latest
The codemod caught most async API changes automatically. It converted our synchronous params access to await params, updated cookies() calls, and flagged areas needing manual attention.
Issues encountered: None at this stage. The codemod worked well.
Hour 2: Manual Async API Fixes
Time: 85 minutes
The codemod missed three patterns:
- Nested component access - Components receiving params as props needed async handling
- Conditional API calls -
if (searchParams.filter)becameif ((await searchParams).filter) - Custom data fetching hooks - Our
useRouteParams()hook needed complete rewrite
This is where realistic time estimates matter. We thought "codemod handles it" meant 10 minutes of work. Actually: 85 minutes reading Next.js docs, understanding async boundaries, and refactoring.
Hour 3: Custom Build Config Migration
Time: 90 minutes
Our SVG loader broke. Spent 45 minutes debugging before realising Turbopack ignores custom Webpack configs. Options:
- Opt out to Webpack (keep old config, lose speed)
- Rewrite for Turbopack compatibility
- Remove custom config and use Next.js defaults
We chose option 3. Removed the custom loader, switched to Next.js SVG handling, inlined critical icons. Cleaner code, faster builds, but required touching 8 components.
Hour 4: Testing and Validation
Time: 65 minutes
- Local testing: 20 minutes
- Build production bundle: 5 minutes (so fast now!)
- Deploy to staging: 10 minutes
- Visual regression testing: 20 minutes
- Performance validation with Lighthouse: 10 minutes
Issues found: One component still accessing params synchronously (missed by codemod). Fixed in 5 minutes.
Total Migration Time: 4 hours
For a medium-sized agency website with:
- 15 routes (5 dynamic)
- Custom build configuration
- Third-party integrations
- Moderate complexity
Your mileage will vary. Simple marketing sites: 2-3 hours. Complex SaaS platforms: plan for 1-2 days, especially if you have extensive custom Webpack config or many third-party packages.
When to Upgrade (And When to Wait)
Not every project should upgrade immediately. Here's a decision framework based on what we learned:
Upgrade Now If:
You're starting a new project. Turbopack is the default—you get all the benefits with none of the migration pain. No-brainer.
You have minimal custom build configuration. If your next.config.js is mostly default settings, migration is straightforward. Budget 2-4 hours.
Build speed is actively hurting productivity. If your team is waiting for builds, complaining about slow Fast Refresh, or avoiding local development because it's sluggish, upgrade. The productivity gain is immediate and ongoing.
You're already using Next.js 15.3+. Much of the groundwork for async APIs was laid in 15.x canary releases. The jump is smaller.
You value staying current. Security patches, new features, and community support focus on latest versions. If you prefer staying up-to-date, upgrade during a low-risk period.
Wait If:
You have extensive custom Webpack configuration. If your build relies heavily on custom loaders, plugins, or transformations, migration is high-effort. Wait until you can dedicate time to refactor or rewrite that configuration for Turbopack compatibility.
Third-party packages aren't compatible yet. Check your critical dependencies against GitHub issues. If key packages haven't confirmed Next.js 16 support, wait.
You're mid-sprint on critical features. Don't upgrade during feature freezes, major launches, or high-pressure periods. The risk of unexpected issues isn't worth it.
Your app is stable and build speed isn't a problem. If builds are fast enough and your team isn't frustrated by development experience, there's no urgency. Wait for the ecosystem to mature and more production battle-testing to happen.
You need time to plan. Version 16 is two weeks old as of this writing. More production issues will emerge. If you're risk-averse, wait 2-3 months for the community to identify and resolve edge cases.
This is about pattern recognition—knowing when "upgrade immediately" patterns apply versus "wait and see" patterns. The pattern here: if you have low build complexity and high build pain, upgrade. If you have high build complexity and low build pain, wait.
What Still Needs Attention
Here's what we didn't cover in our migration but you might need to handle:
Parallel Routes
If you're using parallel routes, all slots now require explicit default.js files. We don't use parallel routes, so this didn't affect us. But it's a breaking change for apps with complex routing structures.
Image Optimization Defaults
Several image optimization defaults changed:
minimumCacheTTLchanged from 60 seconds to 4 hoursimageSizesno longer includes16qualitieschanged to[75]- Local IP optimization blocked by default for security
Review your next.config.js if you rely on specific image optimization behaviour.
Removed Features
These features are completely removed:
- AMP support
next lintcommand (use ESLint directly)devIndicatorsconfig optionsserverRuntimeConfigandpublicRuntimeConfig- Experimental
ppranddynamicIOflags (now stable or renamed)
If you're using any of these, migration is more complex. We weren't, so no impact for us.
Cache Components (Opt-in)
The new Cache Components feature with use cache directive is powerful but opt-in. We haven't enabled it yet because our caching strategy is already working well. This is the right approach: migrate first, then optimise. Don't try to adopt every new feature during migration—too many variables.
The Honest Trade-offs
Let's be clear about what you're getting and what you're giving up:
What You Gain
Significantly faster builds. This is real. 2-3× faster in our experience, consistent with community reports.
Better development experience. Instant Fast Refresh removes friction. You'll notice it immediately.
Clearer async boundaries. Forced async APIs make data flow more explicit. This improves architecture quality over time.
Future-proofing. You're on the latest version with the latest features. Security patches and new capabilities land here first.
Modern defaults. Turbopack, React Compiler support (stable but not default), and Cache Components represent where the ecosystem is heading.
What You're Giving Up (Temporarily)
Stability. Next.js 16 is two weeks old. More issues will emerge. You're an early adopter bearing that risk.
Custom build flexibility. If you relied on Webpack customisation, you either rewrite for Turbopack or opt out and lose speed gains.
Development time. Migration takes 2-8 hours for most projects. That's time not shipping features.
Package compatibility. Some third-party packages haven't caught up yet. You may need to wait for updates or find alternatives.
Simplicity of synchronous code. Async APIs everywhere means more await keywords and understanding of async boundaries. The code is better, but it's more verbose.
As I wrote in embracing productive chaos, there's a balance between stability (staying on known-good versions) and adaptability (adopting new tools that improve productivity). Next.js 16 sits right in that tension zone—it's production-ready per Vercel, but it's not battle-tested across thousands of production apps yet.
What This Means for Your Business
Let's connect the technical changes to business outcomes—because clients don't care about Turbopack. They care about growth.
Faster iteration cycles = faster feature delivery. If your builds are 2× faster and Fast Refresh is instant, your team ships features faster. Estimate conservatively: 5-10% productivity improvement from reduced waiting and context switching. On a team of five developers, that's 2-4 hours per week reclaimed. That's half a sprint per quarter.
Better development experience = better retention. Developer frustration costs money. Slow builds, laggy tooling, and waiting for feedback drives people away. If faster builds make your team happier and more productive, retention improves. In competitive UK tech hiring markets, this matters.
Modern architecture = fewer surprises. Explicit async boundaries catch bugs earlier. Better caching control reduces cache-related production issues. These aren't glamorous benefits, but they reduce firefighting and support burden.
Performance improvements = conversion gains. Faster build tooling often correlates with faster production bundles (though not always). If you ship smaller JavaScript bundles or better-optimised assets, page load times improve. Better performance drives conversion—we've seen this consistently across projects. As I documented in why I built GuardianScan, every 100ms of load time improvement can meaningfully impact conversion rates.
This is the outcomes over outputs lens: it's not about adopting Next.js 16 for its own sake, it's about what that enables. Faster builds let you deliver value to clients faster, which grows their businesses, which makes them successful, which makes you successful.
Practical Recommendations
Based on our migration experience, here's what to do:
Before Migrating
-
Audit your dependencies. Check critical packages against Next.js 16 compatibility. Look for GitHub issues mentioning Next.js 16 problems.
-
Review your build config. If you have custom Webpack configuration, document what it does and why. Decide whether you can rewrite it, replace it with Next.js defaults, or opt out to Webpack temporarily.
-
Test in a branch. Don't upgrade main branch directly. Create a migration branch, test thoroughly, benchmark builds, verify production bundles.
-
Budget realistic time. Simple sites: 2-4 hours. Medium complexity: 4-8 hours. Complex apps: 1-2 days. Plan for 2× your initial estimate.
-
Choose a low-risk window. Not right before a launch, not during a sprint deadline, not when you're short-staffed. Pick a calm period where you can handle unexpected issues.
During Migration
-
Run the codemod first. It catches 80% of async API changes:
npx @next/codemod@canary upgrade latest -
Fix compilation errors before testing. Get the build green first. Don't try to test partially-broken code.
-
Search for patterns the codemod misses. Look for
params.,searchParams.,cookies(),headers()in your codebase. Check if they're properly awaited. -
Test thoroughly. Every dynamic route, every data fetching path, every cache invalidation flow. Runtime errors from async API misuse are sneaky.
-
Benchmark before and after. Measure build times, Fast Refresh speed, production bundle size. Validate that you're getting the promised improvements.
After Migration
-
Monitor production closely. Watch for errors related to async APIs or caching behaviour. The first few days post-deployment reveal issues testing missed.
-
Document what broke. Write down what went wrong and how you fixed it. Future you (and your team) will thank you during the next upgrade.
-
Revisit custom configs later. If you opted out to Webpack to avoid migration complexity, schedule time to revisit and migrate to Turbopack-compatible approaches.
-
Consider Cache Components. Once migration is stable, explore the new
use cachedirective for fine-grained caching control. But don't do this during initial migration—too many variables. -
Share your experience. Write about what worked and what didn't. The Next.js community benefits from real production experiences, not just marketing announcements.
Our Migration: By the Numbers
For Numen Technology:
Build Performance:
- Production builds: 11.3s → 4.2s (2.7× faster)
- Incremental builds: 8.7s → 2.9s (3× faster)
- Fast Refresh: measurably laggy → instant
Migration Effort:
- Total time: 4 hours
- Files changed: 23
- Async API fixes: 15 locations
- Custom config refactored: 1 (SVG handling)
- Production issues: 0 (so far)
Business Impact:
- Developer satisfaction: meaningfully improved
- Build wait time: 350s/day → 150s/day (per developer)
- Feature delivery: unchanged (too early to measure)
- Client impact: none (invisible backend change)
Worth it? Yes. The productivity improvement is real and ongoing. Every build is faster, every code change has instant feedback, and our development experience is noticeably better.
But worth it for everyone? No. If we'd had complex Webpack customisation or tight deadlines, we'd have waited.
Frequently Asked Questions
Moving Forward
Next.js 16 is a significant upgrade with real productivity benefits. The 2-3× faster builds we experienced on numentechnology.co.uk aren't marketing—they're measurable, daily improvements to development experience.
But migration isn't free. The async API changes require careful attention, custom build configurations need rethinking, and you're adopting a framework version that's only two weeks old as of this writing.
Here's the framework: upgrade if the speed gains justify the migration effort and risk. If your team is frustrated by slow builds, if iteration speed matters to your business, if you have relatively simple build configuration—upgrade. The benefits compound daily.
If you have complex Webpack customisation, tight deadlines, or low tolerance for early-adopter risk—wait. Let the ecosystem mature, let more teams battle-test in production, let package maintainers catch up. You'll upgrade in 2-3 months with less friction.
This is pattern recognition in action: knowing when "upgrade immediately" serves you versus when "wait and see" is wiser. There's no universal right answer—only the right answer for your specific context, team, and risk tolerance.
What matters is making that decision consciously, based on evidence and honest assessment, not FOMO or vendor promises.
We upgraded. For us, it was worth it. For you? Use the framework above and decide for yourself.
We upgraded Numen Technology to Next.js 16 and documented what actually happened—including the 4 hours of migration work, the async API changes that broke, and the 2.7× faster builds we measured. If you're considering upgrading your Next.js application or need help navigating framework migrations, we'd be happy to help.
For more on making informed technical decisions, read about pattern matching in decision making and when to trust your patterns versus when to question them.
Get New Posts in Your Inbox
Subscribe to receive new blog posts directly to your email. No spam, unsubscribe anytime.
Enjoyed this article?
If you found this helpful, consider buying me a coffee. It helps me create more content like this!
Get posts like this delivered to your feed reader:
Subscribe via RSS