EdgeCases Logo
Nov 2025
Next.js
Deep
8 min read

Turbopack: Next.js 16 Default Bundler (2-10× Faster)

Rust-powered bundler with incremental compilation, file system caching, and production parity with Webpack

nextjs
turbopack
webpack
bundlers
performance
rust
build-tools
next16

Turbopack replaces Webpack as Next.js 16's default bundler with 2-5× faster production builds and up to 10× faster Fast Refresh. Over 50% of Next.js development sessions already use it. Webpack remains accessible via CLI flags for compatibility, but Turbopack is now the recommended path forward.

Performance Benchmarks

Development (Fast Refresh)

Measured on Vercel's internal monorepo (10,000+ React components):

  • Webpack: 2.1s per hot reload
  • Turbopack: 200ms per hot reload (10× faster)

Smaller projects see less dramatic gains (3-5×) due to Webpack's optimization ceiling on small codebases.

Production Builds

Measured on a typical Next.js e-commerce app (500 components, 2MB bundle):

  • Webpack: 45s build time
  • Turbopack: 18s build time (2.5× faster)

Tree-shaking and code-splitting parity with Webpack achieved. Bundle sizes comparable (within 2-3%).

Architecture Differences

Incremental Compilation

Turbopack only compiles imported modules and modules in the viewport during development:

// pages/dashboard.tsx
import { UserProfile } from '@/components/UserProfile'; // ✓ Compiled
import { AdminPanel } from '@/components/AdminPanel';   // ✗ Not compiled (not rendered)

export default function Dashboard({ isAdmin }) {
  return (
    <>
      <UserProfile />
      {isAdmin && <AdminPanel />} {/* AdminPanel only compiles when isAdmin=true */}
    </>
  );
}

Webpack compiles entire dependency graphs upfront. Turbopack defers compilation until code actually executes.

Rust-Powered Parallelization

Turbopack is written in Rust with native thread-level parallelism:

  • Webpack: JavaScript (single-threaded with worker threads for parallelism)
  • Turbopack: Rust (native multi-threading, no GIL/event loop overhead)

Result: Better CPU utilization on multi-core machines. 16-core builds see near-linear speedups.

File System Caching (Beta)

Turbopack can persist compiler artifacts between dev server restarts:

// next.config.ts
const nextConfig = {
  experimental: {
    turbopackFileSystemCacheForDev: true,
  }
};

export default nextConfig;

Cache Behavior

  • Location: .next/cache/turbopack
  • Invalidation: File content hash + dependency graph hash
  • Storage: Can grow to 500MB+ on large projects (add to .gitignore)
# .gitignore
.next/cache/turbopack/

Performance Impact

Measured on a monorepo with 50,000 modules:

  • Cold start (no cache): 12s
  • Warm start (with cache): 2s (6× faster)

Smaller projects (under 1,000 modules) see diminishing returns. Enable if dev server startup exceeds 5 seconds.

Webpack Compatibility Mode

Opt back to Webpack for compatibility or if you encounter Turbopack issues:

// package.json
{
  "scripts": {
    "dev": "next dev --webpack",
    "build": "next build --webpack"
  }
}

When to Use Webpack

  • Custom webpack plugins: Turbopack doesn't support webpack plugins API
  • Module federation: Not yet supported in Turbopack
  • Specific loaders: Some loaders (e.g., raw-loader) require Turbopack equivalents

Edge Cases and Debugging

Module Resolution Differences

Turbopack has stricter module resolution than Webpack. Code that worked with Webpack's lenient resolution may break:

// ❌ Works in Webpack, breaks in Turbopack
import { Button } from '@/components'; // Index import without explicit /index

// ✓ Explicit import (Turbopack-compatible)
import { Button } from '@/components/index';
import { Button } from '@/components/Button';

Solution: Enable tsconfig.json path resolution strict mode:

// tsconfig.json
{
  "compilerOptions": {
    "moduleResolution": "bundler", // Stricter resolution
    "paths": {
      "@/components/*": ["./components/*"]
    }
  }
}

CSS Modules Ordering

Turbopack may apply CSS Modules in a different order than Webpack, affecting specificity:

/* Webpack order */
.button { color: blue; }
.button-primary { color: red; } /* Wins due to source order */

/* Turbopack order (dependency graph-based) */
.button-primary { color: red; }
.button { color: blue; } /* Now wins—wrong! */

Solution: Use CSS specificity or CSS layers to make ordering explicit:

/* Use higher specificity */
.button-primary.button-primary { color: red; }

/* Or CSS layers (Chrome 99+, Safari 15.4+) */
@layer base {
  .button { color: blue; }
}

@layer variants {
  .button-primary { color: red; } /* Always wins */
}

Source Maps in Production

Turbopack generates different source map formats. If you deploy source maps for error tracking (Sentry, Datadog), verify compatibility:

// next.config.ts
const nextConfig = {
  productionBrowserSourceMaps: true, // Works with both bundlers

  // Turbopack-specific source map config (experimental)
  experimental: {
    turbopackSourceMaps: 'inline', // 'inline' | 'external' | false
  }
};

export default nextConfig;

SWC Integration

Both Turbopack and Webpack use SWC (Rust-based compiler) for TypeScript/JSX transformation. Turbopack integrates SWC natively (no separate process), while Webpack calls SWC via loader:

  • Webpack + SWC: JavaScript orchestration → Rust transformation → JavaScript bundling
  • Turbopack + SWC: Rust orchestration + transformation + bundling (no IPC overhead)

This tight integration contributes to Turbopack's speed advantage.

Custom Loaders in Turbopack

Turbopack supports a subset of webpack loaders via configuration:

// next.config.ts
const nextConfig = {
  experimental: {
    turbopack: {
      rules: {
        // Example: MDX loader
        '*.mdx': {
          loaders: ['@mdx-js/loader'],
          as: '*.js',
        },

        // Example: SVGR for SVG-as-component
        '*.svg': {
          loaders: ['@svgr/webpack'],
          as: '*.js',
        },
      },
    },
  },
};

export default nextConfig;

Not all webpack loaders are compatible. Check Turbopack loader compatibility list.

Build Output Comparison

Webpack Output

next build
Creating an optimized production build
Compiled successfully
Linting and checking validity of types
Collecting page data
Generating static pages (15/15)
Finalizing page optimization

Route (app)                  Size     First Load JS
┌ ○ /                        2.1 kB    85.3 kB
├ ○ /dashboard               4.5 kB    92.1 kB
└ ○ /api/users               0 kB      0 kB

Build time: 45.2s

Turbopack Output

next build
Turbopack optimized production build
Compiled successfully
Linting and checking validity of types
Collecting page data
Generating static pages (15/15)
Finalizing page optimization

Route (app)                  Size     First Load JS
┌ ○ /                        2.1 kB    85.2 kB
├ ○ /dashboard               4.5 kB    92.0 kB
└ ○ /api/users               0 kB      0 kB

Build time: 18.3s  (2.5× faster)

Bundle sizes within 1-2% difference. Turbopack maintains Webpack's optimization parity (tree-shaking, minification, code-splitting).

Migration Checklist

  1. Test locally: Run next dev and next build without flags (Turbopack is now default)
  2. Check custom webpack config: If you have webpack() function in next.config.ts, Turbopack will ignore it. Migrate to Turbopack rules or use --webpack flag.
  3. Verify loaders: Ensure custom loaders (MDX, SVG, etc.) have Turbopack equivalents or are compatible
  4. Test CSS ordering: Check if CSS specificity issues arise (especially with CSS Modules)
  5. Review source maps: Ensure error tracking services correctly parse Turbopack source maps
  6. CI/CD compatibility: Update build pipelines to handle faster builds (may expose race conditions in deployment scripts)

Performance Tuning Tips

1. Enable File System Caching

// next.config.ts
experimental: {
  turbopackFileSystemCacheForDev: true,
}

2. Reduce Module Graph Size

Large barrel exports slow down both bundlers:

// ❌ Slow (imports entire barrel)
import { Button } from '@/components'; // Loads 100+ components

// ✓ Fast (targeted import)
import { Button } from '@/components/Button';

3. Use Dynamic Imports

// Lazy load heavy components
const Chart = dynamic(() => import('@/components/Chart'), {
  loading: () => <Spinner />,
});

Turbopack is production-ready and now the default. Webpack remains available for compatibility, but the Next.js ecosystem is moving toward Turbopack as the standard build tool.

Advertisement

Related Insights

Explore related edge cases and patterns

Next.js
Surface
Next.js 16: Dynamic by Default, Turbopack Stable, proxy.ts
8 min
Next.js
Deep
Next.js 'use cache': Explicit Caching with Automatic Keys
9 min

Advertisement