Next.js 16 Turbopack Production Builds: What Changed and How We Migrated
We'd been watching Turbopack mature since its announcement back in 2022, running it in dev mode since Next.js 14, and cautiously eyeing those "turbo" flags in CI. When Next.js 16 landed in early 2025 with Turbopack as the default production bundler, we knew the migration couldn't wait any longer. We had three client projects on Next.js 15 that needed upgrading, and I'm going to walk you through every significant change, the gotchas we hit, and the performance numbers we saw on the other side.
This isn't a rehash of the release notes. This is what actually happened when we ran next build with Turbopack on real codebases with real complexity.
Table of Contents
- Why Next.js 16 Is a Big Deal
- What Actually Changed with Turbopack in Production
- Build Performance Benchmarks
- Breaking Changes You Need to Know
- Our Migration Process Step by Step
- Webpack Config Translations
- Handling Third-Party Packages
- CSS and Tailwind Considerations
- Deployment and CI Pipeline Updates
- When You Should Not Migrate Yet
- FAQ

Why Next.js 16 Is a Big Deal
Next.js 16 isn't just a version bump. It represents the most significant change to the build infrastructure since the framework moved from pages to the App Router. The headline feature is obvious: Turbopack replaces webpack as the default bundler for both development and production builds.
But there's more going on. Next.js 16 also ships with:
- React 19 as the minimum supported version — no more React 18 compatibility
- Improved streaming and partial prerendering maturity
- New caching defaults that are actually sane (they learned from the Next.js 15 caching backlash)
- Async request APIs fully enforced —
cookies(),headers(), andparamsare all async now with zero legacy sync support - Node.js 20 minimum requirement — Node 18 support is gone
For agencies like ours doing Next.js development, this is the kind of release that touches everything. You can't just bump a version number and call it a day.
What Actually Changed with Turbopack in Production
Let's get specific. During Next.js 14 and 15, Turbopack was available for next dev with the --turbo flag. Production builds still used webpack. Next.js 15.3 introduced an experimental --turbopack flag for next build, and by the time 16 shipped, it became the default.
Here's what's fundamentally different about how Turbopack handles production builds compared to webpack:
Incremental Compilation Architecture
Webpack processes your entire dependency graph on every build. Turbopack uses a function-level caching system built on the Turbo engine (written in Rust). In practice, this means subsequent builds only recompile what actually changed. Your first build might not be dramatically faster, but your second, third, and tenth builds will be.
Tree Shaking Improvements
Turbopack's tree shaking operates at a more granular level than webpack's. We noticed our client bundles were 8-15% smaller on average across our three projects without any code changes. The biggest wins came from barrel file handling — Turbopack is genuinely better at eliminating unused re-exports from index files.
Module Resolution
Turbopack resolves modules differently. It's faster, but it's also stricter. If you had any sloppy import paths that webpack was silently resolving (like missing file extensions in certain edge cases, or case-insensitive paths on macOS that break on Linux), Turbopack will catch them. This caused about 30% of our migration issues.
Code Splitting Strategy
The chunk splitting algorithm is new. Turbopack creates more, smaller chunks by default. This generally improves loading performance for modern browsers with HTTP/2, but it can increase the total number of requests. We saw chunk counts increase by roughly 40% while total bundle size decreased.
SWC Is Now Mandatory
If you were still hanging onto any Babel configuration, it's gone. Turbopack exclusively uses SWC for transformation. This was already the direction things were heading, but Next.js 16 removes any fallback to Babel entirely.
Build Performance Benchmarks
Here are real numbers from our three migration projects. These aren't synthetic benchmarks — they're from actual client applications running in our CI pipeline on GitHub Actions (Ubuntu, 4 vCPU, 16GB RAM runners).
| Metric | Project A (E-commerce) | Project B (SaaS Dashboard) | Project C (Content Site) |
|---|---|---|---|
| Pages/Routes | 847 | 124 | 2,340 |
| webpack build (Next 15) | 4m 32s | 1m 48s | 6m 15s |
| Turbopack build (Next 16, cold) | 3m 10s | 1m 22s | 4m 44s |
| Turbopack build (Next 16, warm cache) | 1m 05s | 28s | 1m 52s |
| Bundle size change | -12% | -8% | -14% |
| JS First Load (homepage) | -18KB | -7KB | -22KB |
| Build memory peak | 3.8GB → 2.9GB | 1.6GB → 1.2GB | 4.2GB → 3.1GB |
The warm cache numbers are the real story. Once Turbopack has built your project once, incremental rebuilds are dramatically faster. For our content-heavy Project C (which uses a headless CMS with thousands of statically generated pages), going from 6+ minutes to under 2 minutes on cached builds was significant. That's real money saved on CI minutes.
Memory usage improvements were also meaningful. Project A was occasionally hitting OOM errors on smaller CI runners with webpack. That problem disappeared with Turbopack.

Breaking Changes You Need to Know
Here's a running list of everything that actually broke during our migrations. The Next.js 16 upgrade guide covers some of these, but a few caught us off guard.
1. Custom Webpack Configuration
This is the big one. If you have a webpack key in your next.config.js, it doesn't work anymore. Turbopack has its own configuration API under turbopack in the Next.js config. Not everything has a 1:1 mapping.
// next.config.js — BEFORE (Next.js 15 with webpack)
module.exports = {
webpack: (config) => {
config.module.rules.push({
test: /\.svg$/,
use: ['@svgr/webpack'],
});
return config;
},
};
// next.config.js — AFTER (Next.js 16 with Turbopack)
module.exports = {
turbopack: {
rules: {
'*.svg': {
loaders: ['@svgr/webpack'],
as: '*.js',
},
},
},
};
2. Synchronous Request APIs Removed
Next.js 15 deprecated synchronous access to cookies(), headers(), params, and searchParams. Next.js 16 removes them entirely. If you ignored those deprecation warnings, you're going to have a bad time.
// BEFORE — this crashes in Next.js 16
export default function Page({ params }) {
const { slug } = params;
return <div>{slug}</div>;
}
// AFTER
export default async function Page({ params }) {
const { slug } = await params;
return <div>{slug}</div>;
}
This seems trivial but it touched over 200 components across our projects. We wrote a codemod to handle most of it (more on that below).
3. React 18 No Longer Supported
Next.js 16 requires React 19. If you have dependencies pinned to React 18, they need updating. Most well-maintained libraries had React 19 support by mid-2025, but we did encounter a couple of holdouts.
4. Node.js 18 Dropped
Minimum is Node.js 20. Update your Docker images, CI configs, and .nvmrc files.
5. next/image Changes
The onLoadingComplete prop is fully removed (was deprecated since Next.js 14). Use onLoad instead. The image optimization pipeline also uses a new underlying library, which means your cached optimized images will regenerate on first request.
Our Migration Process Step by Step
Here's the actual process we followed. We did Project B (smallest) first as a test run, then tackled A and C.
Step 1: Audit Dependencies
Before touching Next.js, we audited every dependency for React 19 and Node.js 20 compatibility. We used a simple script:
npx npm-check-updates --target latest --filter '/react|next/'
We also manually checked our most critical packages: our headless CMS SDK, authentication library, and UI component library.
Step 2: Update Node.js
We updated our .nvmrc to 20.18.0 (latest LTS at the time), updated Dockerfiles, and verified CI runners. Simple but easy to forget.
Step 3: Upgrade React First
npm install react@19 react-dom@19 @types/react@19 @types/react-dom@19
We ran the full test suite here before proceeding. React 19 has its own breaking changes (removed forwardRef as a necessity, ref is now a regular prop, use() hook is stable). We fixed those issues in isolation so we weren't debugging React and Next.js problems simultaneously.
Step 4: Run the Next.js Codemod
Next.js provides an upgrade codemod that handles a lot of the mechanical changes:
npx @next/codemod@latest upgrade
This handled about 70% of the async API migrations automatically. It's not perfect — it struggled with some of our more complex server component patterns — but it saved us hours.
Step 5: Upgrade Next.js
npm install next@16
Step 6: Migrate next.config.js
This was the most time-consuming step for Project A, which had significant webpack customization. I'll cover the specific translations in the next section.
Step 7: Fix Build Errors Iteratively
We ran next build and worked through errors one by one. The error messages from Turbopack are actually better than webpack's in most cases — more specific, with clearer file paths and suggestions.
Step 8: Visual Regression Testing
We use Playwright for E2E tests and ran our visual regression suite to catch any rendering differences. We found two issues: a CSS ordering difference (Turbopack processes CSS imports in a slightly different order than webpack) and a dynamic import that wasn't code-splitting correctly.
Step 9: Performance Validation
We compared Lighthouse scores and Core Web Vitals before and after. Every project improved or stayed neutral. No regressions.
Webpack Config Translations
This section is for teams with custom webpack configs. Here's how common patterns translate to Turbopack.
Custom Loaders
// Turbopack equivalent for custom loaders
module.exports = {
turbopack: {
rules: {
'*.md': {
loaders: ['raw-loader'],
as: '*.js',
},
'*.graphql': {
loaders: ['graphql-tag/loader'],
as: '*.js',
},
},
},
};
Module Aliases
// Resolve aliases work similarly
module.exports = {
turbopack: {
resolveAlias: {
'old-package': 'new-package',
// You can also point to local files
'@legacy/utils': './src/utils/legacy.ts',
},
},
};
What Doesn't Translate
Some webpack plugins simply don't have Turbopack equivalents yet:
webpack.DefinePlugin— Useenvin next.config.js or environment variables directlyBundleAnalyzerPlugin— The@next/bundle-analyzerpackage works with Turbopack as of v16, but the output format changed- Custom chunk splitting via
splitChunks— Turbopack handles this automatically and doesn't expose the same level of control. Honestly, the defaults are good enough for most projects. webpack.IgnorePlugin— UseresolveAliasto point imports to empty modules
Handling Third-Party Packages
A few packages caused issues during migration:
@sentry/nextjs — Needed v9+ for Turbopack compatibility. Earlier versions hooked into webpack internals. The upgrade was straightforward but required config changes.
next-intl — Worked fine after updating to the latest version. The plugin API adapted cleanly.
@vanilla-extract/next-plugin — This was our biggest headache on Project B. Vanilla Extract's webpack plugin didn't have a Turbopack equivalent until their 2.0 release. We had to wait for that or consider alternatives. We waited.
Barrel file packages — Any package that exports hundreds of components from a single index file (looking at you, icon libraries) now gets tree-shaken much more aggressively. This is a good thing, but we saw one case where a dynamically-referenced icon wasn't getting included. We switched from string-based icon lookups to direct imports, which is better practice anyway.
CSS and Tailwind Considerations
If you're using Tailwind CSS (and most of our projects do), the migration is mostly painless. Tailwind v4 works great with Turbopack. But there are a few things to watch:
CSS Import Ordering
Turbopack processes CSS imports in a deterministic but different order than webpack. If you're relying on import order for specificity (and you shouldn't be, but let's be honest — we all end up there sometimes), you might see visual differences. We had one project where a global reset was being overridden by a component CSS module because the import order flipped.
The fix was explicit @layer usage in our CSS, which we should have been doing all along.
CSS Modules
CSS Modules work identically. No changes needed. The generated class names look different (shorter, actually), but that's cosmetic unless you're doing something weird like targeting generated class names in tests.
PostCSS
PostCSS config files are still respected. Your postcss.config.js continues to work. No changes needed here.
Deployment and CI Pipeline Updates
Our deployment targets are primarily Vercel and AWS (via SST/OpenNext). Here's what changed:
Vercel: Automatically detected Next.js 16 and used Turbopack. Build cache integration works out of the box. Our build times on Vercel dropped even more dramatically than local CI because Vercel has deep integration with Turbopack's caching layer. Project C went from ~8 minutes to ~2.5 minutes on Vercel.
AWS/OpenNext: Required updating to OpenNext 4.x, which added Turbopack output support. The output format changed slightly — the .next directory structure is reorganized — so any post-build scripts that referenced specific file paths needed updating.
Docker builds: If you're building Next.js in Docker, update your base image to Node 20+ and be aware that Turbopack's cache directory (.next/cache/turbopack) should be included in your Docker layer caching strategy. We added a specific COPY layer for this.
# Optimize Docker layer caching for Turbopack
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
# Cache mount for Turbopack
RUN --mount=type=cache,target=/app/.next/cache \
npm run build
When You Should Not Migrate Yet
I don't want to paint this as all roses. There are legitimate reasons to stay on Next.js 15 for now:
- Heavy webpack plugin dependence: If your build relies on 3+ custom webpack plugins that don't have Turbopack equivalents, the migration cost might not be worth it yet.
- Monorepo with shared webpack config: If you're sharing webpack config across Next.js and non-Next.js projects in a monorepo, splitting that config is additional work.
- Stability requirements: Next.js 16.0 is stable, but 16.1 and 16.2 fixed real bugs. I'd wait for at least 16.2+ before migrating anything you can't tolerate downtime on.
- Legacy dependencies stuck on React 18: If a critical dependency hasn't added React 19 support, you're blocked regardless.
For teams doing headless CMS development, the migration is generally smoother because CMS-driven sites tend to have simpler build configurations.
FAQ
Is Turbopack stable enough for production in Next.js 16? Yes. Turbopack has been in development for over three years, was battle-tested in dev mode across millions of Next.js projects, and went through an extended beta in production builds during Next.js 15.3-15.5. We've been running it in production since the 16.0 release across multiple client sites with zero bundler-related issues. That said, if you're on 16.0 specifically, upgrade to 16.2+ where several edge-case bugs were resolved.
Can I still use webpack with Next.js 16? Not as the primary bundler, no. Turbopack is the only supported bundler in Next.js 16. If you absolutely need webpack, you'll need to stay on Next.js 15, which will receive security patches through early 2026. Vercel has been clear that webpack support in Next.js is done.
How much faster is Turbopack compared to webpack for production builds? On cold builds (no cache), we saw 20-30% improvements. On warm/cached builds, the improvement jumps to 50-70%. The exact numbers depend heavily on project size, number of routes, and the amount of static generation happening. Memory usage also dropped 20-30% consistently across our projects.
Do I need to rewrite my next.config.js for Turbopack?
If you have custom webpack configuration in your next.config.js, yes — those blocks need to be translated to the turbopack configuration format. If you're only using standard Next.js config options (images, redirects, rewrites, env variables), those all work exactly the same. The migration effort is proportional to how much custom webpack config you have.
Will my existing CI/CD pipeline work with Next.js 16?
Mostly yes. The main things to update are: Node.js version (minimum 20), any scripts that reference webpack-specific output files, and any caching strategies that target .next/cache/webpack. You'll want to cache .next/cache/turbopack instead. If you're deploying to Vercel, everything is handled automatically.
Does Turbopack support all the same features as webpack in Next.js? For Next.js-specific features, yes — App Router, Pages Router, API routes, middleware, ISR, SSG, SSR all work. For custom webpack configuration, there's about 90% feature parity. The remaining gaps are mostly around niche webpack plugins and highly custom chunk splitting strategies. Check the Turbopack compatibility docs for your specific use case.
Should I migrate to Next.js 16 or consider alternatives like Astro? It depends on your use case. If you're building highly interactive applications with complex state management, Next.js 16 is a strong choice and the Turbopack improvements make the DX significantly better. If you're building content-heavy sites with minimal interactivity, Astro remains an excellent alternative with its partial hydration model. We've been building with both and choosing based on project requirements. If you're unsure, reach out to us and we can help you evaluate.
What's the minimum time needed to migrate a medium-sized Next.js 15 app to 16? For a typical medium-sized application (50-200 routes, standard dependencies, minimal custom webpack config), budget 2-4 days of developer time. That includes dependency updates, async API migrations, testing, and deployment verification. If you have extensive custom webpack configuration or legacy dependencies, it could take a week or more. Our team at Social Animal offers migration services if you'd rather not burn your team's sprint on infrastructure work.