Introduction
Turbopack is Vercel's Rust-based bundler designed as the successor to Webpack. Built on the same principles as Turbo (incremental computation), Turbopack promises 700x faster updates than Webpack and 10x faster than Vite for large applications. It's the default bundler in Next.js 14+ for development, and it represents a fundamental shift in how JavaScript bundling works.
The JavaScript bundler landscape has evolved dramatically over the past decade. Webpack dominated from 2015 to 2020, Rollup became the standard for libraries, Parcel offered zero-configuration bundling, esbuild introduced Go-powered speed, and Vite revolutionized the development experience with native ESM. Turbopack enters this landscape with a unique proposition: apply the same incremental computation principles that made Turborepo fast to the bundling problem itself.
This guide covers Turbopack's architecture, how it achieves its performance claims, practical migration strategies from Webpack, and when to use Turbopack vs alternatives like Vite or esbuild. We'll examine the technical foundations that make Turbopack fast, walk through real-world configuration scenarios, and provide honest benchmarks comparing Turbopack to its competitors.
Why Turbopack?
The Problem with Webpack
Webpack processes your entire dependency graph on every change. For a large application with thousands of modules, even a single character change triggers a cascade of transformations:
File changed → Parse all dependencies → Transform all modules → Generate bundles → Write output
This approach doesn't scale. A change to one file shouldn't require reprocessing the entire dependency graph. In a typical enterprise application with 30,000 modules, a Webpack rebuild after a single file change can take 3-5 seconds. Over the course of a development day with hundreds of saves, those seconds add up to minutes of lost productivity.
The fundamental issue is that Webpack's architecture was designed for a world where bundling was a one-time build step. Modern development requires continuous, incremental rebuilding that responds to sub-second file changes. Webpack's plugin system, while incredibly flexible, introduces overhead that compounds at scale. Each plugin processes every module, even when only one module changed.
Turbopack's Solution: Incremental Computation
Turbopack uses a technique called function-level caching with an incremental computation engine. Instead of rebuilding everything, Turbopack:
- Caches the result of every computation
- Only recomputes what changed and its dependents
- Shares cached results across builds
File changed → Identify affected computations → Recompute only those → Update output
This is the same approach that makes Turbo (the monorepo build system) fast, applied to bundling. The key insight is that bundling is fundamentally a computation graph: each module transformation depends on its source code and its dependencies. When a module changes, only transformations that depend on it need to rerun.
Architecture
The Turbo Engine
At the core of Turbopack is the Turbo engine, which provides an incremental computation framework written in Rust. The engine tracks every computation as a pure function with explicit inputs and outputs. When an input changes, the engine invalidates only the computations that directly depend on it, then lazily recomputes them on demand.
// Conceptual model of incremental computation
interface TurboEngine {
// Each computation is a function with inputs
compute<T>(fn: () => T, inputs: any[]): CachedResult<T>;
// When inputs change, only recomputes affected functions
invalidate(inputs: any[]): void;
// Parallelizes independent computations
parallelize<T>(computations: (() => T)[]): T[];
}The Turbo engine maintains a dependency graph of all computations. Each computation is identified by a unique key and has a list of input dependencies. When a file changes, the engine walks the dependency graph to find all computations that transitively depend on that file. It invalidates those computations and marks them for recomputation. When a computation is requested, the engine checks if it's cached and valid—if so, it returns the cached result; if not, it recomputes, caches, and returns the result.
Module Resolution
Turbopack resolves modules using Rust, which is significantly faster than JavaScript-based resolvers. The resolver handles TypeScript paths, Node.js module resolution, and package.json exports fields entirely in native code.
// Turbopack resolves imports in parallel
// import { Button } from './components/Button';
// import { Modal } from './components/Modal';
// Both resolve simultaneously, not sequentially
// Turbopack also handles complex resolution scenarios:
import { utils } from '@myorg/utils'; // Package.json exports
import styles from './Button.module.css'; // CSS Modules
import data from './config.json'; // JSON importsTree Shaking
Turbopack performs tree shaking at the module level, eliminating unused exports. Unlike Webpack which tree-shakes at the bundle level, Turbopack can eliminate unused code during the module transformation phase, reducing the amount of code that needs to be processed in subsequent steps.
// utils.ts
export function used() { return 'used'; }
export function unused() { return 'unused'; }
// app.ts
import { used } from './utils';
// 'unused' is eliminated from the bundle at the module levelMemory Management
Rust's ownership model provides automatic memory management without garbage collection. This eliminates the GC pauses that can affect JavaScript-based bundlers during large builds. Turbopack's memory usage scales linearly with the number of active modules, not the total number of modules in the project.
Performance Benchmarks
Cold Start
Cold start performance measures how long it takes to start the development server from scratch. Turbopack's advantage comes from parallel module resolution and transformation.
| Bundler | 1,000 modules | 10,000 modules | 30,000 modules |
|---|---|---|---|
| Webpack 5 | 2.5s | 15s | 45s |
| Vite (esbuild) | 0.8s | 3s | 8s |
| Turbopack | 0.3s | 1.2s | 2.5s |
| Rspack | 0.5s | 2s | 5s |
Hot Updates (HMR)
HMR performance measures how quickly changes appear in the browser after saving a file. This is where Turbopack's incremental computation truly shines.
| Bundler | 1,000 modules | 10,000 modules | 30,000 modules |
|---|---|---|---|
| Webpack 5 | 200ms | 1.5s | 4s |
| Vite (esbuild) | 50ms | 150ms | 400ms |
| Turbopack | 5ms | 15ms | 30ms |
| Rspack | 30ms | 100ms | 250ms |
Turbopack's HMR performance scales sub-linearly because it only recomputes the affected module and its direct dependents, not the entire graph. For a 30,000 module project, changing a leaf module triggers recomputation of only that module—typically under 10ms.
Memory Usage
| Bundler | 10,000 modules | 30,000 modules |
|---|---|---|
| Webpack 5 | 800MB | 2.1GB |
| Vite | 400MB | 1.2GB |
| Turbopack | 300MB | 800MB |
Turbopack's Rust-based memory management keeps memory usage lower than JavaScript-based alternatives, particularly for large projects.
Using Turbopack with Next.js
Enable Turbopack
# Next.js 14+ — Turbopack is the default for dev
next dev --turbo
# Or in package.json
{
"scripts": {
"dev": "next dev --turbo",
"build": "next build"
}
}Configuration
// next.config.js
module.exports = {
experimental: {
turbo: {
// Custom loaders
rules: {
'*.svg': {
loaders: ['@svgr/webpack'],
as: '*.js',
},
'*.md': {
loaders: ['./loaders/markdown.js'],
as: '*.js',
},
},
// Resolve aliases
resolveAlias: {
'@components': './src/components',
'@utils': './src/utils',
'@hooks': './src/hooks',
},
// Module extensions
resolveExtensions: ['.tsx', '.ts', '.jsx', '.js', '.mjs'],
// Environment variables to include in cache key
env: {
API_URL: process.env.API_URL,
FEATURE_FLAGS: process.env.FEATURE_FLAGS,
},
},
},
};CSS Support
Turbopack has built-in support for CSS, CSS Modules, PostCSS, and Tailwind CSS:
// CSS Modules work out of the box
import styles from './Button.module.css';
function Button({ children }) {
return <button className={styles.button}>{children}</button>;
}
// Global CSS imports
import './globals.css';
// Tailwind CSS works without additional configurationPostCSS Configuration
// postcss.config.js — Turbopack reads this automatically
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};Migration from Webpack
Step 1: Install Next.js 14+
npm install next@latest react@latest react-dom@latestStep 2: Update Configuration
Most Webpack configurations have direct Turbopack equivalents:
// webpack.config.js (before)
module.exports = {
resolve: {
alias: {
'@components': path.resolve(__dirname, 'src/components'),
},
},
module: {
rules: [
{ test: /\.svg$/, use: ['@svgr/webpack'] },
],
},
};
// next.config.js (Turbopack equivalent)
module.exports = {
experimental: {
turbo: {
resolveAlias: {
'@components': './src/components',
},
rules: {
'*.svg': { loaders: ['@svgr/webpack'], as: '*.js' },
},
},
},
};Step 3: Replace Custom Loaders
Turbopack supports most Webpack loaders, but some custom loaders may need updates:
// Webpack loader (works in Turbopack)
module.exports = function(source) {
return source.replace(/FOO/g, 'bar');
};
// Turbopack-native transform (Rust, faster)
// Use when performance matters for frequently-transformed filesStep 4: Update Environment Variables
// Webpack: process.env.REACT_APP_*
// Turbopack/Next.js: process.env.NEXT_PUBLIC_*
// For server-side variables, use process.env directly
// For client-side variables, prefix with NEXT_PUBLIC_Migration Strategy
Migrating from Webpack to Turbopack requires a phased approach to minimize risk. Start by enabling Turbopack for local development only while keeping Webpack for production builds. This lets your team benefit from faster development iteration without risking production stability. Audit your Webpack configuration to identify plugins and loaders that may not have Turbopack equivalents. Common Webpack plugins like DefinePlugin, CopyWebpackPlugin, and MiniCssExtractPlugin have direct Turbopack counterparts or built-in equivalents. Custom loaders may need to be rewritten as Turbopack transforms or replaced with existing alternatives. Test your application thoroughly after enabling Turbopack to catch any behavioral differences in module resolution, CSS processing, or asset handling. Gradually expand Turbopack usage to CI builds and eventually production as compatibility improves and your team gains confidence.
Turbopack vs Vite vs esbuild
| Feature | Turbopack | Vite | esbuild |
|---|---|---|---|
| Language | Rust | JavaScript + esbuild (Go) | Go |
| Dev server | Custom | esbuild + native ESM | No dev server |
| Production build | Planned (Rust) | Rollup | Standalone |
| HMR | Function-level caching | Native ESM invalidation | N/A |
| Large apps | Excellent scaling | Good scaling | Fast builds |
| Ecosystem | Next.js focused | Framework agnostic | Low-level tool |
| Maturity | Newer | More mature | Mature |
| Configuration | Minimal | Minimal to moderate | API-based |
When to Use Turbopack
- Next.js projects — Turbopack is the default and best-supported option
- Very large codebases — Turbopack's incremental computation scales better
- Frequent HMR — Sub-30ms updates even for massive projects
- Memory-constrained environments — Rust memory management is more efficient
When to Use Vite
- Non-Next.js projects — Vite works with any framework (Vue, Svelte, React, etc.)
- Smaller projects — Vite's simplicity is an advantage
- Production builds — Vite's Rollup-based production builds are battle-tested
- Plugin ecosystem — Vite has a larger community plugin ecosystem
When to Use esbuild Directly
- Library bundling — esbuild is excellent for bundling libraries
- Script processing — Fast transformation of individual files
- CI pipelines — Minimal setup for build-only tasks
Advanced Configuration
Custom Loaders
// next.config.js
module.exports = {
experimental: {
turbo: {
rules: {
'*.md': {
loaders: ['raw-loader'],
as: '*.js',
},
'*.graphql': {
loaders: ['graphql-tag/loader'],
as: '*.js',
},
'*.glsl': {
loaders: ['raw-loader'],
as: '*.js',
},
},
},
},
};Source Maps
// next.config.js
module.exports = {
experimental: {
turbo: {
sourceMaps: true,
},
},
};Debugging Build Issues
# Enable verbose logging
NEXT_TURBOPACK_LOGGING=verbose next dev --turbo
# Profile build performance
next dev --turbo --profileWhen Turbopack encounters issues, several debugging approaches help identify the root cause. Use the --profile flag to generate a detailed performance profile that shows where time is spent during the build. Enable verbose logging with the NEXT_TURBOPACK_LOGGING environment variable to see detailed information about module resolution and transformation. Check the Turbopack compatibility page in the Next.js documentation for known limitations and workarounds.
Under the Hood: How Incremental Computation Works
The Turbo engine maintains a dependency graph of all computations. Each computation is identified by a unique key and has a list of input dependencies. When a file changes, the engine walks the dependency graph to find all computations that transitively depend on that file. It invalidates those computations and marks them for recomputation. When a computation is requested, the engine checks if it's cached and valid—if so, it returns the cached result; if not, it recomputes, caches, and returns the result.
This approach has several advantages over traditional rebuild strategies. First, it naturally handles transitive dependencies: if module A depends on module B which depends on module C, changing C invalidates both B and A. Second, it supports lazy recomputation: invalidated computations are only recomputed when actually requested, so changing a module that nobody imports has zero cost. Third, it enables parallel recomputation: independent invalidated computations can be recomputed simultaneously across multiple CPU cores.
Integration with Development Workflows
Turbopack integrates seamlessly with modern development workflows. In Next.js, Turbopack is the default development bundler starting from version fourteen. Simply run next dev and Turbopack handles module resolution, transformation, and hot module replacement automatically. For monorepo setups, Turbopack works alongside Turborepo to provide fast builds across multiple packages. The bundler respects workspace boundaries and can efficiently handle cross-package dependencies without redundant processing.
CI pipelines benefit from Turbopack's fast cold start times, reducing the time spent waiting for development server startup during integration testing. Docker-based development environments see particular benefits because Turbopack's efficient memory usage reduces container resource requirements.
Best Practices
- Use Turbopack for Next.js dev — It's the default and optimized for Next.js
- Keep configuration minimal — Turbopack works well with defaults
- Profile build times — Use
--profileflag to identify bottlenecks - Report issues — Turbopack is actively developed; report bugs to help improve it
- Stay updated — Turbopack improves with each Next.js release
- Test CSS thoroughly — CSS Modules and PostCSS may behave differently
- List environment variables — Include all env vars that affect caching in config
- Clear cache when debugging — Delete
.nextdirectory when troubleshooting - Use TypeScript paths — Configure tsconfig paths to match Turbopack aliases
- Monitor memory usage — Rust memory management is efficient but monitor for leaks
Common Pitfalls
| Pitfall | Impact | Solution |
|---|---|---|
| Webpack-only plugins | Build errors | Check Turbopack compatibility docs |
| Custom loaders not supported | Build errors | Rewrite as Turbopack-compatible loader |
| Missing environment variables | Runtime errors | Use NEXT_PUBLIC_ prefix for client-side vars |
| CSS processing differences | Style issues | Test CSS Modules and PostCSS thoroughly |
| Production build limitations | Build failures | Use Webpack for production in Next.js 14 |
| Module resolution differences | Import errors | Verify tsconfig paths match Turbopack config |
| Stale cache after config changes | Unexpected behavior | Clear .next directory and restart dev server |
Future Roadmap
The Turbopack team has outlined an ambitious roadmap for future development. Production build support is a top priority, with the goal of replacing Webpack entirely for Next.js production builds. Persistent caching across development sessions will further reduce startup times by reusing previously computed module graphs. Plugin support will allow third-party tools to extend Turbopack's functionality without modifying the core bundler. Improved error messages and diagnostics will make it easier to identify and fix build issues. The team is also working on better support for monorepo setups, including efficient handling of cross-package dependencies and shared configurations.
Conclusion
Turbopack represents the future of JavaScript bundling. Its Rust-based incremental computation engine delivers order-of-magnitude improvements in both cold start and HMR performance. While it's still maturing, Turbopack is already the best choice for Next.js development and will continue to improve.
The key insight is that Turbopack's approach—function-level caching with incremental computation—scales sub-linearly with codebase size. As your application grows, Turbopack's advantages become more pronounced. A 30,000 module project sees a 100x improvement in HMR speed compared to Webpack, while a 1,000 module project sees a 40x improvement. The performance gap widens as complexity increases.
For teams building large Next.js applications, Turbopack is not just an incremental improvement—it's a fundamental shift in development experience. The sub-second feedback loop transforms how developers work, enabling faster iteration and reducing the friction between writing code and seeing results.
Key takeaways:
- Incremental computation — Only recomputes what changed, not the entire graph
- Rust-powered — Native performance for module resolution and transformation
- 700x faster HMR — Sub-30ms updates even for 30,000+ module projects
- Next.js default — Turbopack is the default bundler for Next.js 14+ dev
- Configuration compatible — Most Webpack configs map directly to Turbopack
- Still maturing — Production builds and some features are still being developed
- Memory efficient — Rust's ownership model eliminates GC pauses and reduces memory usage