The Complete Guide to React Native Build Optimization

The Mythical Engineer


If you’re building React Native apps on Linux or macOS, you’ve probably noticed that release builds for Android take far longer than they should. Open your system’s activity monitor during a build and you’ll likely see the surprising culprit: most build tools are utilizing only a single CPU core, leaving the rest of your powerful machine entirely idle.

This guide walks you through each optimization step, explains why it works, and shows you how to verify the improvements.

Note: The optimizations in this guide have been tested on a MacBook Air M1 with 8GB RAM. Results may vary depending on your system specifications.

Why Are Builds So Slow?

React Native’s build stack consists of several components:

By default, all three are configured conservatively. Gradle runs tasks sequentially, Metro uses minimal workers, and C++ files get recompiled fresh on every build—even if nothing changed. The good news: each of these is easily fixable.

Initial runtime duration without any optimization

BUILD SUCCESSFUL in 21m 14s
730 actionable tasks: 554 executed, 166 from cache, 10 up-to-date
Configuration cache entry stored.

See the profiling report at: file:///.../android/build/reports/profile/profile-2026-03-13-07-45-14.html
A fine-grained performance profile is available: use the --scan option.

===========================================
                RESULTS
===========================================
⏱️  Total Benchmarking Time: 22m 2s
📊 A detailed Gradle profiling report is available at:
   android/build/reports/profile/
   (Open the latest .html file in a browser to see where the time was spent)
===========================================

1. Optimize Gradle for Parallel Execution

Gradle is the Android build system. Out of the box, it runs tasks one after another and restricts memory to avoid destabilizing your system. On a modern multi-core machine, this is a massive waste.

The Fix

optimize-gradle.sh - This script automatically detects your system’s specifications and optimizes gradle.properties:

#!/bin/bash

# Path to the gradle.properties file
GRADLE_PROPERTIES="android/gradle.properties"

if [ ! -f "$GRADLE_PROPERTIES" ]; then
    echo "Error: gradle.properties not found at $GRADLE_PROPERTIES"
    exit 1
fi

# Get system specifications
TOTAL_MEM_BYTES=$(sysctl -n hw.memsize)
HALF_MEM_MB=$(( TOTAL_MEM_BYTES / 2 / 1024 / 1024 ))
EIGHTH_MEM_MB=$(( TOTAL_MEM_BYTES / 8 / 1024 / 1024 ))
CPU_CORES=$(sysctl -n hw.ncpu)
WORKERS_MAX=$(( CPU_CORES * 3 / 4 ))
if [ $WORKERS_MAX -lt 1 ]; then WORKERS_MAX=1; fi

# Desired property values
JVM_ARGS="-Xmx${HALF_MEM_MB}m -XX:MaxMetaspaceSize=${EIGHTH_MEM_MB}m"
PARALLEL="true"

echo "Optimizing Android Gradle Properties for faster builds..."
echo " - CPU Cores count: $CPU_CORES (Using $WORKERS_MAX for 75%)"
echo " - Memory (Half RAM): ${HALF_MEM_MB}MB"
echo " - Metaspace (1/8 RAM): ${EIGHTH_MEM_MB}MB"

# Remove existing properties so we don't have duplicates
sed -i '' '/^org\.gradle\.jvmargs/d' "$GRADLE_PROPERTIES"
sed -i '' '/^org\.gradle\.parallel/d' "$GRADLE_PROPERTIES"
sed -i '' '/^org\.gradle\.workers\.max/d' "$GRADLE_PROPERTIES"

# Append optimized properties at the end of the file
echo "org.gradle.jvmargs=$JVM_ARGS" >> "$GRADLE_PROPERTIES"
echo "org.gradle.parallel=$PARALLEL" >> "$GRADLE_PROPERTIES"
echo "org.gradle.workers.max=$WORKERS_MAX" >> "$GRADLE_PROPERTIES"

echo "Success: gradle.properties optimized!"

Usage:

chmod +x scripts/optimize-gradle.sh
./scripts/optimize-gradle.sh

Or manually add these settings to android/gradle.properties:

# Enable parallel task execution
org.gradle.parallel=true

# Use 75% of your CPU cores
org.gradle.workers.max=6

# Allocate more memory: 4GB heap + 1GB metaspace
org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m
Setting What It Does
org.gradle.parallel=true Runs independent build tasks simultaneously
org.gradle.workers.max Max parallel threads Gradle can use
org.gradle.jvmargs JVM heap + metaspace allocation

Gains after optimizing gradle config

BUILD SUCCESSFUL in 10m 54s
730 actionable tasks: 452 executed, 268 from cache, 10 up-to-date
Configuration cache entry stored.

See the profiling report at: file:///.../android/build/reports/profile/profile-2026-03-13-08-15-54.html
A fine-grained performance profile is available: use the --scan option.

===========================================
                RESULTS
===========================================
⏱️  Total Benchmarking Time: 11m 24s
📊 A detailed Gradle profiling report is available at:
   android/build/reports/profile/
   (Open the latest .html file in a browser to see where the time was spent)
===========================================

~ 22 minutes -> ~ 11 minutes (Almost twice as fast)

Why it works: Gradle can now execute multiple build phases (compiling, packaging, linting) at the same time instead of waiting for each to finish before starting the next.


2. Speed Up Metro Bundler

Metro compiles your JavaScript into a bundle for release builds. By default, it uses only 1 worker—regardless of how many cores your system has.

The Fix

In your metro.config.js, add dynamic worker allocation:

const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
const os = require('os');

const config = {
  // Use 50% of available CPU cores dynamically
  maxWorkers: Math.max(1, Math.floor(os.cpus().length * 0.5)),
};

module.exports = mergeConfig(getDefaultConfig(__dirname), config);

Gains after metro bundler config optimization

BUILD SUCCESSFUL in 5m 31s
730 actionable tasks: 452 executed, 268 from cache, 10 up-to-date
Configuration cache entry stored.

See the profiling report at: file:///.../android/build/reports/profile/profile-2026-03-13-09-50-26.html
A fine-grained performance profile is available: use the --scan option.

===========================================
                RESULTS
===========================================
⏱️  Total Benchmarking Time: 5m 49s
📊 A detailed Gradle profiling report is available at:
   android/build/reports/profile/
   (Open the latest .html file in a browser to see where the time was spent)
===========================================

~ 11 minutes -> ~ 5 minutes (Almost four times fast)

Why it works: Instead of hardcoding a worker count, this automatically scales to your machine. On an 8-core system, Metro now uses 4 workers instead of 1—potentially 4x faster JS bundling.


3. Cache C++ Compilations with ccache

React Native’s New Architecture relies heavily on C++ code (TurboModules, Fabric). These files are expensive to compile, and by default, they’re rebuilt from scratch every single build—even if you only changed a single JavaScript file.

The Fix

Install and configure ccache:

brew install ccache
ccache --set-config=max_size=20G

setup-ccache.sh - This script automatically checks for ccache, installs it if needed, and configures it for React Native builds:

#!/bin/bash

echo "==========================================="
echo "  Checking ccache (Compiler Cache) Status  "
echo "==========================================="

if command -v ccache &> /dev/null; then
    echo "✅ ccache is already installed."
else
    echo "⚠️ ccache is not installed. Installing via Homebrew..."
    if command -v brew &> /dev/null; then
        brew install ccache
        if [ $? -eq 0 ]; then
            echo "✅ ccache successfully installed."
        else
            echo "❌ Failed to install ccache. Please install it manually: brew install ccache"
            exit 1
        fi
    else
        echo "❌ Homebrew is not installed. Cannot automatically install ccache."
        echo "Please install Homebrew (https://brew.sh) or install ccache manually."
        exit 1
    fi
fi

# Configure ccache for React Native Android builds
ccache --set-config=max_size=20G
echo "✅ ccache max size configured to 20G."

# Show current stats
echo ""
echo "Current ccache stats:"
ccache -s

echo "==========================================="
echo "  ccache is ready for React Native Builds  "
echo "==========================================="

How it works: ccache sits between your compiler and the build system. It caches compiled output by hashing the source file and compiler flags. On subsequent builds, identical files are served from cache instead of recompiled.

Verifying Your Cache

After running a build, check your hit rate:

ccache -s

Look for the cache hit rate percentage. A warm cache typically shows 70-90% hit rates after the first build, meaning that percentage of C++ files were skipped entirely.

> bash scripts/setup-ccache.sh

===========================================
  Checking ccache (Compiler Cache) Status
===========================================
✅ ccache is already installed.
✅ ccache max size configured to 20G.

Current ccache stats:
Cacheable calls:   394 /  422 (93.36%)
  Hits:            178 /  394 (45.18%)
    Direct:        178 /  178 (100.0%)
    Preprocessed:    0 /  178 ( 0.00%)
  Misses:          216 /  394 (54.82%)
Uncacheable calls:  28 /  422 ( 6.64%)
Local storage:
  Cache size (GB): 0.0 / 20.0 ( 0.22%)
  Hits:            178 /  394 (45.18%)
  Misses:          216 /  394 (54.82%)
===========================================
  ccache is ready for React Native Builds
===========================================

> bash scripts/optimize-gradle.sh

Optimizing Android Gradle Properties for faster builds...
 - CPU Cores count: 8 (Using 6 for 75%)
 - Memory (Half RAM): 4096MB
 - Metaspace (1/8 RAM): 1024MB
Success: gradle.properties optimized!

BUILD SUCCESSFUL in 40s
730 actionable tasks: 32 executed, 698 up-to-date
Configuration cache entry stored.

Now builds are down to seconds 🎉.

~ 5 minutes -> < 1 minutes (Blazingly fast)

Why it works: Your second build only recompiles files that actually changed. Everything else loads from cache—in some cases, reducing build times by 50% or more.


4. Build Only the Active Architecture

By default, React Native builds for every Android CPU architecture: x86, arm64, armeabi. That’s three times the work. While developing, you only need the architecture matching your device or emulator.

The Fix

In package.json, add the --active-arch-only flag:

"scripts": {
  "dev": "react-native run-android --mode debug --active-arch-only"
}

Why it works: React Native queries ADB to detect your connected device’s architecture, then only compiles for that target. Debug builds can become 2-3x faster.

Or use the automated benchmark-build.sh script to measure your build performance:

#!/bin/bash

# Configuration
BUILD_TYPE=${1:-"release"} # Default to release if no argument is provided
START_TIME=$(date +%s)

echo "==========================================="
echo "  React Native Android Build Benchmarking  "
echo "==========================================="
echo "Build Type: assemble$BUILD_TYPE"
echo ""

# Step 1: Clean Metro Bundler Cache
echo "🧹 [1/5] Clearing Metro Bundler Cache..."
rm -rf $TMPDIR/metro-*
rm -rf $TMPDIR/haste-map-*
echo " -> Metro cache cleared."

# Step 2: Clean Gradle Cache
echo "🧹 [2/5] Clearing Gradle Daemon & Cache..."
cd android || exit
./gradlew --stop > /dev/null 2>&1
rm -rf .gradle
rm -rf app/build
./gradlew clean > /dev/null 2>&1
cd ..
echo " -> Gradle cache cleared."

# Step 3: Clean Watchman Cache (if installed)
if command -v watchman &> /dev/null; then
    echo "🧹 [3/5] Watchman found. Clearing Watchman cache..."
    watchman watch-del-all > /dev/null 2>&1
    echo " -> Watchman cache cleared."
else
    echo "⏩ [3/5] Watchman not installed. Skipping..."
fi

echo ""
echo "🚀 [4/5] Proceeding with Build..."
echo "Starting Gradle Build with --profile..."

# Step 5: Execute build with profiling enabled
cd android || exit
# Using --profile to generate an HTML report of the build process
./gradlew assemble$BUILD_TYPE --profile

# Calculate total time
END_TIME=$(date +%s)
DURATION=$((END_TIME - START_TIME))
MINUTES=$((DURATION / 60))
SECONDS=$((DURATION % 60))

echo ""
echo "==========================================="
echo "                RESULTS                    "
echo "==========================================="
echo "⏱️  Total Benchmarking Time: ${MINUTES}m ${SECONDS}s"
echo "📊 A detailed Gradle profiling report is available at:"
echo "   android/build/reports/profile/"
echo "   (Open the latest .html file in a browser to see where the time was spent)"
echo "==========================================="
cd ..

Usage:

chmod +x scripts/benchmark-build.sh
./scripts/benchmark-build.sh        # Uses default release
./scripts/benchmark-build.sh debug  # Benchmark debug build

Quick Start: Run All Optimizations

Here’s the complete workflow to implement all optimizations in your React Native project:

# 1. Create scripts directory and copy the scripts from this guide
mkdir -p scripts

# 2. Setup ccache (run once)
chmod +x scripts/setup-ccache.sh
./scripts/setup-ccache.sh

# 3. Optimize Gradle (run once or after fresh clone)
chmod +x scripts/optimize-gradle.sh
./scripts/optimize-gradle.sh

# 4. Update metro.config.js for Metro optimization
# Add dynamic worker allocation (see Section 2)

# 5. Add active architecture flag to package.json
# Add "--active-arch-only" to your dev script (see Section 4)

# 6. Build with benchmarking
chmod +x scripts/benchmark-build.sh
./scripts/benchmark-build.sh

After first build, subsequent builds will be dramatically faster due to ccache hits.


Summary

Optimization Expected Improvement
Gradle parallel + memory 30-50% faster Android builds
Metro maxWorkers 2-4x faster JS bundling
ccache 50%+ faster after first build
Active architecture 2-3x faster debug builds

Combine all four, and you can realistically expect release builds to go from 20+ minutes down to 2-5 minutes on a modern multi-core system and often faster on Apple Silicon.

Run your first optimized build and monitor the results. Your future self will thank you.


#react-native  #android  #build-performance  #gradle  #metro 

Suggested Reading

  • * How to get rid of Glance Android app

  • Share this: