The Complete Guide to React Native Build Optimization
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:
- Gradle — builds your Android native code
- Metro — bundles your JavaScript
- C++ Compiler — handles TurboModules and Fabric (New Architecture)
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