Turbopack in Production: Next.js 15 Speed Wins
Share this article
- Premium Results
- Publish articles on SitePoint
- Daily curated jobs
- Learning Paths
- Discounts to dev tools
7 Day Free Trial. Cancel Anytime.
JavaScript bundling has long been the performance ceiling for large Next.js projects. Slow hot module replacement, painful cold starts exceeding 10 seconds on sizeable codebases, and expensive CI builds that eat through compute minutes have defined the developer experience for years. Turbopack's stabilization in Next.js 15 delivers the largest measured speed improvement the framework has shipped since the App Router: Vercel's benchmarks report up to 76% faster cold starts compared to Webpack.
Turbopack is no longer hidden behind an experimental flag for development. As of Next.js 15, it ships as a stable, default-ready bundler for next dev, with active progress toward production build support. This tutorial covers the full path: enabling Turbopack, configuring it for real-world stacks, benchmarking actual performance gains, troubleshooting common breakages, and assessing production readiness.
Table of Contents
- What Is Turbopack and Why It Matters
- Enabling Turbopack in Your Next.js 15 Project
- Benchmarking the Speed Wins
- Configuring Turbopack for Your Stack
- Production Readiness: Where Turbopack Stands Today
- Troubleshooting Common Issues
- Complete Implementation Checklist
- Summary and Next Steps
What Is Turbopack and Why It Matters
Turbopack vs. Webpack: Architecture at a Glance
Turbopack is a Rust-based incremental computation engine designed as Webpack's successor within the Next.js ecosystem. Where Webpack processes and bundles modules through a JavaScript-based pipeline, Turbopack uses Rust's performance characteristics alongside two key architectural decisions: function-level caching and lazy bundling. Function-level caching (where each transformation step is individually memoized by its inputs) means Turbopack remembers the result of every computation and only recomputes what has changed. Lazy bundling means it only processes modules actually requested by the browser, rather than eagerly building an entire dependency graph upfront.
This is distinct from tools like Vite, which uses esbuild for dependency pre-bundling and native ES modules for dev serving. Turbopack takes a different approach, operating as a unified incremental engine rather than composing multiple tools.
Function-level caching (where each transformation step is individually memoized by its inputs) means Turbopack remembers the result of every computation and only recomputes what has changed.
Stability Milestones in Next.js 15
Turbopack's journey began as an experimental flag in Next.js 13, matured through Next.js 14 with expanded test coverage, and reached stable status for next dev in Next.js 15. Turbopack is marked stable because it passed the Next.js integration test suite at the time of the Next.js 15 release. Check the Next.js 15 release notes for the exact pass-rate threshold and methodology. The next build --turbo path remains in active development. To check its current status, visit the Turbopack API reference or run npx next build --turbo and inspect the warning banner in the output. If still experimental, Next.js prints a warning; if stable, no warning appears.
Enabling Turbopack in Your Next.js 15 Project
Fresh Project Setup
Starting from scratch is the simplest path. Creating a new project and launching the dev server with Turbopack requires just two commands:
npx create-next-app@latest my-turbo-app
cd my-turbo-app
npx next dev --turbo
This is the zero-config happy path. Next.js 15's scaffolding produces a project structure fully compatible with Turbopack out of the box, with no additional configuration files or dependencies required.
Migrating an Existing Project
For existing projects, the migration involves updating the dev script in package.json and reviewing next.config.js for any Webpack-specific configuration that needs translation:
{
"scripts": {
"dev": "next dev --turbo",
"build": "next build",
"start": "next start",
"lint": "next lint"
}
}
If the project uses a next.config.js with custom Webpack configuration, those customizations will need to be migrated to Turbopack's configuration surface (covered in the configuration section below). Before running, verify the project meets the minimum requirements: Next.js 15 or later and Node.js 18.18.0 or later. Run npm ls next or yarn why next to confirm peer dependency alignment โ look for next@15.x.x in the output to confirm the installed version โ particularly if the project uses packages that pin specific Next.js versions.
Verifying Turbopack Is Active
After launching the dev server, the terminal output explicitly confirms which bundler is running. Look for this in the startup log:
โฒ Next.js 15.x.x
- Local: http://localhost:3000
- Turbopack ready
The presence of "Turbopack ready" (as opposed to the standard Webpack compilation messages) confirms the switch took effect. If the output shows "compiled successfully" with Webpack-style chunk information instead, Turbopack is not active and the --turbo flag may not be reaching the dev command correctly. Double-check the package.json script or pass the flag directly via the command line.
Benchmarking the Speed Wins
Cold Start Time
Measuring cold start time requires a controlled, reproducible approach. The following shell script compares startup times between Webpack and Turbopack by clearing caches and timing the initial server ready event:
#!/bin/bash
# benchmark-cold-start.sh
# Requires bash 4+. macOS users: brew install bash. Windows users: use WSL2.
# Run from project root only.
PORT=3099
WAIT_TIMEOUT=60000
clear_caches() {
[ -d ".next" ] && rm -rf .next
[ -d "node_modules/.cache" ] && rm -rf node_modules/.cache
}
run_timed() {
local label="$1"
local turbo_flag="$2"
clear_caches
echo "--- ${label} ---"
npx next dev --port "$PORT" ${turbo_flag} &
DEV_PID=$!
START=$(date +%s%N)
if ! npx wait-on --timeout "$WAIT_TIMEOUT" "http://localhost:${PORT}"; then
echo "ERROR: dev server did not start within timeout." >&2
kill "$DEV_PID" 2>/dev/null
return 1
fi
END=$(date +%s%N)
echo "${label}: $(( (END - START) / 1000000 ))ms"
kill "$DEV_PID" 2>/dev/null
wait "$DEV_PID" 2>/dev/null
}
run_timed "Webpack Cold Start" ""
run_timed "Turbopack Cold Start" "--turbo"
Vercel's published benchmarks report up to 76% faster cold starts with Turbopack compared to Webpack. This figure comes from Vercel's Turbopack benchmarks page (see turbo.build/pack/docs/benchmarks for methodology details, including project size and hardware specifications). Real-world results will vary depending on machine hardware, project size, dependency count, and operating system. Run the script above against your own project and compare to your Webpack baseline rather than treating 76% as a guaranteed figure.
Hot Module Replacement (HMR)
Turbopack's HMR speed advantage stems directly from its incremental computation model. When a developer edits a deeply nested component, Turbopack does not re-bundle the entire application or even the entire route. It recomputes only the functions affected by the change and sends a minimal update to the browser. In practice, this means editing a component five levels deep in a component tree typically produces a browser update in under 200ms for single-file edits, rather than the multi-second delay common in large Webpack projects.
To measure this yourself, open the browser devtools Network panel, filter by HMR events, edit a deeply nested component, and note the elapsed milliseconds between save and update. The difference is most noticeable on projects with hundreds of modules.
Editing a component five levels deep in a component tree typically produces a browser update in under 200ms for single-file edits, rather than the multi-second delay common in large Webpack projects.
Large Codebase Considerations
Speed gains from Turbopack scale non-linearly with project size. A small project with 20 routes and minimal dependencies may see under 20% improvement, because Webpack was already fast enough. Projects with 500+ routes, deep component trees, and a complex dependency graph are where Turbopack's lazy bundling and function-level caching show the largest gap. To quantify this for your codebase, run the cold-start benchmark on both a small route subset and the full project, then compare the ratios. The key factors are route count, component tree depth, and the total number of nodes in the dependency graph.
Configuring Turbopack for Your Stack
Custom Webpack Configurations: What Carries Over
Turbopack provides its own config keys under turbopack in next.config.js. This key requires Next.js 15. Verify your installed version with npm ls next to confirm the key is recognized โ if you see an "Invalid next.config.js option" warning on startup, consult the Next.js docs for your specific version's config structure. Common Webpack customizations like loader rules and path aliases translate to this new structure:
// next.config.js
const path = require('path');
/** @type {import('next').NextConfig} */
const nextConfig = {
turbopack: {
rules: {
'**/*.svg': {
loaders: ['@svgr/webpack'], // VERIFY: confirm @svgr/webpack Turbopack loader compat
as: '*.js',
},
},
resolveAlias: {
'@components': path.resolve(__dirname, './src/components'),
'@utils': path.resolve(__dirname, './src/utils'),
},
},
};
module.exports = nextConfig;
This example demonstrates two of the most common migration needs: handling SVG imports through a loader and configuring path aliases. The rules key replaces Webpack's module.rules for specifying loaders, while resolveAlias replaces resolve.alias. Note that @svgr/webpack is a Webpack-specific loader; verify its compatibility with Turbopack's loader interface before relying on it in your project. If issues arise, consider @svgr/rollup or a native Turbopack transform as alternatives. The resolveAlias values must be absolute paths โ use path.resolve(__dirname, '...') rather than bare relative strings, which resolve against an internal working directory and produce silent module-not-found failures.
Handling Unsupported Webpack Plugins
Turbopack does not support plugins that deeply hook into Webpack's compilation lifecycle, such as webpack-bundle-analyzer or custom plugins using compiler.hooks. For projects that depend on these during development, the graceful fallback is maintaining a second script in package.json:
{
"scripts": {
"dev": "next dev --turbo",
"dev:webpack": "next dev"
}
}
This allows developers to drop back to Webpack for specific edge cases without abandoning Turbopack as the default development experience.
Environment Variables and Feature Flags
Environment variable handling via .env, .env.local, .env.development, and .env.test files works identically under Turbopack. Turbopack resolves .env files in the same order as Webpack and handles the NEXT_PUBLIC_ prefix identically. That said, verifying environment variable availability in the browser after switching to Turbopack is a worthwhile smoke test, particularly for projects that rely on build-time variable inlining.
Production Readiness: Where Turbopack Stands Today
next dev --turbo vs. next build --turbo
The critical distinction: next dev --turbo is stable and tested. next build --turbo is in active development and may still carry experimental or alpha status. This means production builds currently still use Webpack. This split works fine in practice. The development bundler and the production bundler do not need to be the same tool, and Next.js has always maintained separate optimization paths for dev and build. Developers get Turbopack's speed during iteration while production output continues to use Webpack's mature optimization pipeline.
CI/CD Pipeline Adjustments
CI/CD configurations should use Turbopack for development server tasks (running tests against the dev server, smoke tests) while keeping the standard build for production artifacts. Ensure the dev script in package.json includes the --turbo flag (as shown in the migration section above), since Turbopack is activated via the CLI flag, not via environment variables:
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '18.18.0'
cache: 'npm'
- run: npm ci
- run: | npm run dev -- --port 3099 &
echo $! > /tmp/dev.pid
shell: bash
- run: npx wait-on --timeout 60000 http://localhost:3099
- run: npm test
- name: Cleanup dev server
if: always()
run: kill $(cat /tmp/dev.pid) 2>/dev/null || true
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '18.18.0'
cache: 'npm'
- run: npm ci
- run: npm run build
This workflow runs tests against the Turbopack dev server for speed while the production build step uses Webpack for stability. The dev server process PID is captured so cleanup runs reliably even if a prior step fails.
Testing Parity
Run your full test suite under Turbopack's dev server before committing to the switch. The goal is surfacing any bundler-specific differences. Common gotchas include dynamic imports that behave differently under lazy bundling (for example, a dynamic(() => import(...)) call may resolve in a different chunk order, causing hydration mismatches in tests), custom Babel transforms that need SWC equivalents, and CSS-in-JS libraries (particularly styled-components and Emotion) that require specific SWC transform settings in next.config.js rather than Babel plugins.
Troubleshooting Common Issues
"Module Not Found" After Enabling Turbopack
If you see Module not found: Can't resolve '@components/...' immediately after switching, the problem is almost always path alias resolution. Turbopack resolves aliases from tsconfig.json (or jsconfig.json) paths, but subtle differences in how wildcards and base URLs are interpreted can cause modules to go unresolved. Verify that tsconfig.json paths match exactly what Turbopack expects, including trailing /* patterns on directory aliases. Also ensure that any resolveAlias entries in next.config.js use absolute paths (via path.resolve(__dirname, '...')) rather than bare relative strings.
Styling Breakages
Check these in order: CSS Modules and Tailwind CSS v3.x work with Turbopack without additional configuration. Tailwind CSS v4 users should verify PostCSS plugin compatibility, as v4 uses a different configuration model. Sass requires confirming that the loader is configured under the turbopack.rules key in next.config.js. For styled-components or Emotion, verify that the SWC transform settings are enabled in next.config.js under the compiler key, as Turbopack uses SWC rather than Babel for these transforms.
Third-Party Package Incompatibilities
When a package fails silently or throws an unfamiliar error, enable diagnostic tracing first. Setting NEXT_TURBOPACK_TRACING=1 before launching the dev server produces trace logs that help identify which module or transform is failing. If no trace output appears, consult the Turbopack troubleshooting docs for the current variable name, as it may change across versions. Report confirmed incompatibilities to the Turbopack GitHub repository to help the team prioritize fixes.
Complete Implementation Checklist
- Confirm Next.js 15+ and Node.js 18.18.0+ are installed.
- Update the
package.jsondev script tonext dev --turbo. - Migrate custom Webpack loaders to the
next.config.jsturbopackkey, verifying each loader's Turbopack compatibility individually. - Verify Turbopack activation by checking for "Turbopack ready" in terminal output.
- Benchmark cold start and HMR against the Webpack baseline using timed runs.
- Run the full test suite under the Turbopack dev server and diff the results against your Webpack test output.
- Audit third-party packages for compatibility; enable
NEXT_TURBOPACK_TRACINGfor diagnostics. - Update CI/CD pipelines: Turbopack for dev/test steps, standard build for production.
- Monitor
next build --turbodevelopment progress for future full adoption. - Document any fallback configurations and known incompatibilities for the team so the next person does not rediscover them.
Summary and Next Steps
Turbopack in Next.js 15 delivers measurable speed wins where they matter most: cold start times reduced by up to 76% according to Vercel's benchmarks (see turbo.build/pack/docs/benchmarks for methodology), sub-200ms HMR for single-file edits through incremental computation, and a noticeably faster feedback loop during development. Use Turbopack for development today, keep Webpack for production builds until next build --turbo reaches stable, and benchmark against your own project baselines rather than relying on generic numbers.
Use Turbopack for development today, keep Webpack for production builds until
next build --turboreaches stable, and benchmark against your own project baselines rather than relying on generic numbers.
Start here: run npx next dev --turbo, open your largest page, and time the cold start against your current Webpack baseline.
- Premium Results
- Publish articles on SitePoint
- Daily curated jobs
- Learning Paths
- Discounts to dev tools
7 Day Free Trial. Cancel Anytime.
