◉RORK LABJP
●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Beta since 09/15 with no stable date yet; running projects in Expo Go has required login since 09/03●WIDGET — A write-up traces an iOS widget that renders black only in production builds●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SDK 58 — Beta since 09/15 with no stable date yet; running projects in Expo Go has required login since 09/03●WIDGET — A write-up traces an iOS widget that renders black only in production builds●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…
Articles/Dev Tools
⬡ Dev Tools/2026-06-01Intermediate

Fixing 'JavaScript heap out of memory' in Metro and EAS Builds

Your Rork or Expo build dies with 'FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory.' Here is why it happens and exactly how to fix it, both locally and on EAS Build.

Metro4EAS Build22Memory5Expo219Troubleshooting43

I was building one of my wallpaper apps the other day when the bundle got almost ninety percent of the way through and the terminal suddenly went red: FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory. Restarting didn't help — it died at the same point every time. I run six wallpaper apps in parallel, and only the ones whose asset count had crept past a certain size ran into this.

The important thing to understand is that this error is not telling you your code is wrong. It is telling you that Node.js ran out of the memory it was given. Because the cause sits outside your source code, staring at the message alone rarely points you anywhere useful. Below I'll walk through where to make the fix — for both the local Metro bundler and EAS Build — so you can recover with the least friction.

Isolate the symptom and when it happens

There are three classic moments this error appears: during bundling with npx expo start, during an eas build cloud build right before the Gradle/Xcode stage while the bundle is generated, and while writing the production bundle with expo export.

What they share is a single moment where a large number of JavaScript modules get expanded at once. Node.js ships with a default heap ceiling (roughly 2GB to 4GB depending on version and environment), and a large app with many assets hits that ceiling first. In a sense, this error is proof that your app has grown.

Your first move is to confirm it really is a memory problem. Raise the ceiling temporarily before the build command, and if it passes, you have your answer.

# Temporarily raise the heap ceiling to 8GB and see if it still fails
NODE_OPTIONS=--max-old-space-size=8192 npx expo start --clear

If it stops dying, memory was the cause. If it still fails even with this, you need to suspect something else — a circular import or an infinite loop, for example.

Fix 1: Raise the local heap ceiling permanently

Typing the environment variable every time isn't realistic, so bake it into your package.json scripts. That keeps the behavior consistent across machines and teammates, and avoids forgotten flags.

{
  "scripts": {
    "start": "NODE_OPTIONS=--max-old-space-size=8192 expo start",
    "export": "NODE_OPTIONS=--max-old-space-size=8192 expo export"
  }
}

On Windows the NODE_OPTIONS=... prefix won't work, so wrap it with cross-env to make it portable across operating systems.

{
  "scripts": {
    "start": "cross-env NODE_OPTIONS=--max-old-space-size=8192 expo start"
  }
}

Use roughly half your installed RAM as the target. On a 16GB machine, 8192 (8GB) is a safe figure; pushing past your physical memory triggers swapping and actually slows things down. On my Mac it stabilized at 8192, and raising it further made no difference.

Fix 2: For EAS Build, raise the resource class

If the failure happens in the cloud build rather than locally, setting NODE_OPTIONS on your own machine does nothing. You need more memory on the EAS Build machine itself. Set resourceClass on each profile in eas.json.

{
  "build": {
    "production": {
      "android": { "resourceClass": "large" },
      "ios": { "resourceClass": "large" },
      "env": {
        "NODE_OPTIONS": "--max-old-space-size=8192"
      }
    }
  }
}

The key here is to raise the machine's memory with resourceClass while also lifting Node's heap ceiling through env. Bumping only the machine memory still leaves Node capped low, so the build dies at the same spot. Both have to move together for the fix to take. Note that large may fall outside the free tier, so check your billing plan first.

Fix 3: Reduce what goes into the bundle in the first place

Adding memory is treating the symptom. If your app is going to keep growing, structurally reducing the number of modules in the bundle pays off longer. Enabling inline requires in metro.config.js keeps the number of modules loaded at once lower.

// metro.config.js
const { getDefaultConfig } = require('expo/metro-config');
 
const config = getDefaultConfig(__dirname);
 
config.transformer.getTransformOptions = async () => ({
  transform: {
    experimentalImportSupport: false,
    inlineRequires: true,
  },
});
 
module.exports = config;

For image assets, keep large PNGs out of the JS bundle by swapping any require-referenced images for optimized versions. When I moved my wallpaper apps' high-resolution images to remote delivery, the memory the bundler consumed dropped visibly. Not hoarding assets locally is, in my experience, the single most effective long-term prevention.

If it dies in Android's Gradle step, suspect a different ceiling

Here's a confusing one: an Android build can throw a similar out-of-memory error that is actually Gradle (the JVM) running out of heap, not Node.js. If the log says Java heap space or GC overhead limit exceeded rather than JavaScript heap, you're raising the wrong ceiling. In that case, adjust the JVM arguments in android/gradle.properties.

# android/gradle.properties
org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m

No amount of bumping NODE_OPTIONS will touch this. Just remember that one word in the error message — JavaScript versus Java — tells you where to fix it, and you'll avoid a lot of wasted trial and error. Early on I confused the two and burned time poking at the Node side. Once I trained myself to read the subject of the log line first, recovery got much faster.

Habits that keep it from coming back

This error tends to show up unannounced precisely when your app is doing well. I've developed apps solo since 2014, and on the way to a cumulative 50 million downloads I hit this same wall every time features and assets piled up. That's why I now put NODE_OPTIONS into the CI build profile from day one, and make a habit of checking the change in bundle size whenever I add assets.

As your next step, add NODE_OPTIONS=--max-old-space-size=8192 locally and see whether the build gets through. If it does, all that's left is folding it into the permanent settings above, and you'll stay clear of this error for a good while. I hope this helps if you've been stuck watching it fail at the same spot.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

⬡ Dev Tools2026-09-21
A build that stops with exit code 0 — the flag that silenced the log, and the check that let an empty file through
Two reports landed in the same week: eas build --local stopping with exit code 0 but produced no further output, and a zero-byte stub that never got embedded. Both came down to settings that reduce output. Here is how to audit your own scripts.
⬡ Dev Tools2026-10-15
When EXPO_PUBLIC_ Values Read Fine in Dev but Come Back undefined in Production: Telling the Spellings Apart
The API URL reads fine while developing and turns undefined once the app is built. The cause was how process.env was written. I lined up babel-preset-expo's output for dev and production and tabulated which spellings get replaced and which are left behind.
⬡ Dev Tools2026-09-07
Adding .easignore stops EAS from reading .gitignore — count what actually ships to the build
EAS Build decides what to upload from .gitignore, and the moment you add .easignore the two swap places. Here is how I count the bundled files before sending, plus a measured result: once a parent directory is excluded, an exclamation mark cannot bring a file back.