nextjs - 💡(How to fix) Fix Turbopack dev: 6-8GB RSS on Apple Silicon caused by Wasmer/Cranelift JIT (IOAccelerator MAP_JIT pages) [1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
vercel/next.js#91585Fetched 2026-04-08 00:57:52
View on GitHub
Comments
1
Participants
2
Timeline
4
Reactions
0
Author
Timeline (top)
closed ×1commented ×1labeled ×1locked ×1

Root Cause

Root Cause Analysis

Code Example

- Next.js: 16.1.7
- Node.js: v22.21.1 / v20.19.0 (same behavior)
- macOS: Darwin 25.3.0 (Apple Silicon M1 Max, 36GB RAM)
- @next/swc-darwin-arm64: 16.1.7

---

Region Type                  Resident MB  (regions)
IOAccelerator                      6130      (139)THIS IS THE PROBLEM
mapped file                         345       (80)
__TEXT                               300      (426)
Memory Tag 255 (Rust/mimalloc)       296     (1209)
__OBJC_RO                             60        (1)
MALLOC_SMALL                          50       (25)

---

pub fn create_turbo_tasks(
    output_path: PathBuf,
    persistent_caching: bool,
    _memory_limit: usize,       // ← PREFIXED WITH UNDERSCORE = UNUSED
    dependency_tracking: bool,
    is_ci: bool,
    is_short_session: bool,
) -> Result<NextTurboTasks> {
    // _memory_limit is never referenced in the function body
RAW_BUFFERClick to expand / collapse

Link to the code that reproduces this issue

Any Next.js 16.x project with Turbopack (default bundler) on Apple Silicon macOS.

To Reproduce

  1. npx next dev on any non-trivial Next.js 16 project on Apple Silicon
  2. Open 2-3 pages in the browser
  3. Check RSS with ps aux | grep next-server

Current vs. Expected behavior

Current: next-server process consumes 6-8GB RSS after compiling a few pages. On 8GB machines this causes OOM.

Expected: A dev bundler should not exceed 1-2GB RSS.

Provide environment information

- Next.js: 16.1.7
- Node.js: v22.21.1 / v20.19.0 (same behavior)
- macOS: Darwin 25.3.0 (Apple Silicon M1 Max, 36GB RAM)
- @next/swc-darwin-arm64: 16.1.7

Root Cause Analysis

The 6-8GB is NOT V8 heap — it's IOAccelerator (MAP_JIT) pages

Using vmmap to analyze the next-server process after compiling 2 pages:

Region Type                  Resident MB  (regions)
IOAccelerator                      6130      (139)    ← THIS IS THE PROBLEM
mapped file                         345       (80)
__TEXT                               300      (426)
Memory Tag 255 (Rust/mimalloc)       296     (1209)
__OBJC_RO                             60        (1)
MALLOC_SMALL                          50       (25)

V8's JS heap is only ~300MB (process.memoryUsage().heapUsed). The 6.1GB is in IOAccelerator regions — macOS's label for MAP_JIT mmap pages on Apple Silicon.

IOAccelerator growth correlates with page compilation

StateRSSIOAccelerator regions
SWC binary loaded (no compilation)49 MB66 (1.4 MB total)
After compiling /2,600 MB97
After compiling / + /pay3,800 MB104
After browsing 5+ pages6,900 MB141

Each compiled page adds ~7 IOAccelerator regions that grow from 16KB to 64-128MB each (all rw-/rwx SM=PRV).

Source: Wasmer + Cranelift JIT compiled into SWC binary

The next-swc.darwin-arm64.node binary (100MB) statically links:

  • Wasmer 6.1.0-rc3 (337 string references in binary)
  • Cranelift code generator (178 references)
  • wasmer-compiler-cranelift-6.1.0-rc.3
  • swc_plugin_runner-23.0.0

When Turbopack compiles modules, Cranelift JIT-compiles code into MAP_JIT pages. On Apple Silicon, these pages are labeled IOAccelerator by macOS and grow without bound.

experimental.turbopackMemoryLimit is accepted but NEVER used

In crates/next-napi-bindings/src/next_api/turbopack_ctx.rs:

pub fn create_turbo_tasks(
    output_path: PathBuf,
    persistent_caching: bool,
    _memory_limit: usize,       // ← PREFIXED WITH UNDERSCORE = UNUSED
    dependency_tracking: bool,
    is_ci: bool,
    is_short_session: bool,
) -> Result<NextTurboTasks> {
    // _memory_limit is never referenced in the function body

The parameter is passed from JS config but silently ignored. BackendOptions has no memory limit field either.

What we tried (none helped with IOAccelerator)

ApproachResult
--max-old-space-size=1024Only limits V8 heap (300MB), no effect on IOAccelerator
Disable V8 JIT (--no-sparkplug --no-turbofan)No change — IOAccelerator is from Cranelift, not V8
MIMALLOC_PURGE_DELAY=0Only affects Memory Tag 255 (mimalloc), not IOAccelerator
RAYON_NUM_THREADS=1No change
turbopackMemoryLimit: 2GBAccepted in config output but ignored (see above)
turbopackFileSystemCacheForDev: trueHelps on restart (2.6GB vs 6.8GB cold) but grows back as pages are visited

Suggested fixes

  1. Implement the _memory_limit parameter — it's already wired through from JS config but unused in Rust
  2. Lazy-initialize Wasmer/Cranelift — don't allocate JIT pools unless SWC WASM plugins are actually configured
  3. Use Singlepass compiler instead of Cranelift for dev mode — single-pass compilation uses dramatically less memory
  4. Free Cranelift JIT pages after compilation is complete — the compiled WASM code doesn't need to persist in executable memory after the transform is done
  5. Cap IOAccelerator/MAP_JIT pool size — Cranelift should have a configurable limit on JIT code cache

Which area(s) are affected?

  • Turbopack
  • Memory usage
  • Developer experience on constrained hardware

Which stage(s) are affected?

  • next dev (Turbopack)

Additional context

The turbopackFileSystemCacheForDev: true experimental flag significantly helps with restart memory (avoids cold recompilation), but doesn't prevent growth during a session as new pages are visited. It should be the default for dev.

This issue affects all Apple Silicon Macs but is most critical on 8GB machines where the 6-8GB RSS causes system OOM. The same IOAccelerator pattern likely occurs on other ARM64 platforms.

extent analysis

Fix Plan

To address the excessive memory usage by IOAccelerator regions in Turbopack on Apple Silicon macOS, we will focus on implementing a memory limit for the Cranelift JIT compiler.

  1. Implement the _memory_limit parameter:

    • Modify the create_turbo_tasks function in next-napi-bindings/src/next_api/turbopack_ctx.rs to use the _memory_limit parameter.
    • Set a default memory limit if none is provided in the config.
  2. Lazy-initialize Wasmer/Cranelift:

    • Only initialize Wasmer and Cranelift when SWC WASM plugins are actually configured.
  3. Use Singlepass compiler for dev mode:

    • Switch to the Singlepass compiler for development mode to reduce memory usage.
  4. Free Cranelift JIT pages after compilation:

    • Ensure that Cranelift JIT pages are freed after compilation is complete.

Here's an example of how the modified create_turbo_tasks function could look:

pub fn create_turbo_tasks(
    output_path: PathBuf,
    persistent_caching: bool,
    memory_limit: usize, // Remove the underscore to use the parameter
    dependency_tracking: bool,
    is_ci: bool,
    is_short_session: bool,
) -> Result<NextTurboTasks> {
    // Use the memory_limit parameter to set a limit for Cranelift
    let turbo_tasks = NextTurboTasks::new(
        output_path,
        persistent_caching,
        memory_limit, // Pass the memory limit to the NextTurboTasks constructor
        dependency_tracking,
        is_ci,
        is_short_session,
    );
    // ...
}

In the NextTurboTasks constructor, you can then use the memory_limit parameter to configure Cranelift:

impl NextTurboTasks {
    fn new(
        output_path: PathBuf,
        persistent_caching: bool,
        memory_limit: usize,
        dependency_tracking: bool,
        is_ci: bool,
        is_short_session: bool,
    ) -> Result<Self> {
        // Configure Cranelift with the provided memory limit
        let cranelift_config = CraneliftConfig::new(memory_limit);
        // ...
    }
}

Verification

To verify that the fix worked, you can:

  • Run npx next dev and monitor the memory usage of the next-server process using ps aux | grep next-server.
  • Check the IOAccelerator regions using vmmap to ensure they are within the expected limits.
  • Test the application with multiple pages and verify that the memory usage does not exceed the expected limits.

Extra Tips

  • Consider setting a default memory limit for development mode to prevent excessive memory usage.
  • Monitor the memory usage of the `next-server

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING