What's New in React Native 0.79: Features, Performance Wins, and How to Migrate
React Native 0.79 is a big one. With 944 commits from over 100 contributors, this release touches just about every part of the developer experience — from how fast your bundler starts up to how your Android app launches. Whether you're running a production app or kicking off something new, there's a lot to like here.
So, let's break it all down.
I'll walk you through the major features, show you the real performance numbers, flag the breaking changes you'll need to deal with, and lay out a practical migration path so you can upgrade without the usual headaches.
Metro 0.82: Up to 3x Faster Startup Times
This is the one you'll feel right away. React Native 0.79 ships with Metro bundler 0.82, and if you've ever watched yarn start crawl through initialization in a large monorepo, you're going to appreciate what deferred hashing brings to the table.
Understanding Deferred Hashing
Previously, Metro would eagerly compute file hashes during startup — scanning and hashing every single file in your project tree before the dev server became available. For big projects with thousands of source files, this meant sitting there for several seconds (or minutes, in truly massive monorepos) before you could start working.
Metro 0.82 flips this approach with deferred hashing. Instead of pre-computing all hashes upfront, Metro now initializes the file watcher and makes the dev server available almost immediately. Hashes get computed lazily on first access and cached from there.
The practical impact is significant:
- Small projects (under 500 files): Startup improves by roughly 30-50%
- Medium projects (500-2000 files): Around 2x faster
- Large monorepos (2000+ files): Up to 3x faster or more
- CI/CD pipelines: Build initialization is noticeably faster, trimming total pipeline time
Honestly, the monorepo improvement alone makes this upgrade worth it for many teams.
Package.json Exports and Imports: Now Stable
Another big change in Metro 0.82 is that package.json "exports" and "imports" field resolution has moved from experimental to stable. Both are now enabled by default for all projects on React Native 0.79.
The "exports" field has actually been supported since React Native 0.72, and "imports" support came through a community contribution. With 0.79, both are the default resolution strategy:
{
"name": "my-library",
"exports": {
".": {
"import": "./dist/esm/index.js",
"require": "./dist/cjs/index.js",
"react-native": "./dist/react-native/index.js"
},
"./utils": {
"import": "./dist/esm/utils.js",
"require": "./dist/cjs/utils.js"
}
},
"imports": {
"#platform": {
"react-native": "./src/platform/native.js",
"default": "./src/platform/web.js"
}
}
}
This is a major step toward aligning React Native with Node.js module resolution standards. But — and this is important — it's also a potential breaking change. Some packages that haven't properly configured their "exports" field may break when Metro starts actually respecting it. More on how to handle this in the migration section below.
Handling Exports-Related Breakage
If you run into packages that don't properly define their "exports" field, you can add resolution overrides in your Metro config as a temporary workaround:
// metro.config.js
const { getDefaultConfig } = require('@react-native/metro-config');
const config = getDefaultConfig(__dirname);
config.resolver.unstable_enablePackageExports = true;
// Add specific package workarounds if needed
config.resolver.resolveRequest = (context, moduleName, platform) => {
// Custom resolution logic for problematic packages
if (moduleName === 'problematic-package/subpath') {
return {
filePath: require.resolve('problematic-package/dist/subpath.js'),
type: 'sourceFile',
};
}
return context.resolveRequest(context, moduleName, platform);
};
module.exports = config;
Faster Android App Startup: Uncompressed JavaScript Bundles
Here's a clever optimization for Android: React Native 0.79 now ships JavaScript bundles uncompressed inside APKs. This feature, developed by Marc Rousavy, directly improves time-to-interactive (TTI) on Android devices.
Why Uncompressed Bundles Are Faster
Previously, the JS bundle inside an Android APK was compressed using standard ZIP compression. That saved download size, but it came with a hidden cost: during app startup, the device had to decompress the entire bundle into memory before Hermes could even begin parsing it.
On mid-range and budget devices — which make up a huge chunk of the Android market — this decompression step was a real bottleneck. By shipping the bundle uncompressed, 0.79 just eliminates that overhead entirely.
The results, measured on a Samsung Galaxy A14 (a popular budget device):
- Time-to-interactive improvement: Up to 400ms faster (~12% improvement)
- Memory pressure: Reduced peak memory during startup since the OS can memory-map the uncompressed bundle directly
- Trade-off: Slightly larger APK, but APK download from Google Play still uses store-level compression
Configuring Bundle Compression
You can control this through your app/build.gradle file:
// app/build.gradle
android {
defaultConfig {
// ...
}
}
react {
// Set to false (default in 0.79) for faster startup
// Set to true if APK size is a critical concern
enableBundleCompression = false
}
For most apps, the default (uncompressed) is the right call. The APK size increase is typically modest because Google Play applies its own compression during distribution. Only consider enabling bundle compression if you're distributing APKs outside the Play Store and size is absolutely paramount.
Understanding the Size vs. Speed Trade-Off
To put the size impact in perspective: a typical React Native JS bundle ranges from 2MB to 10MB compressed. Stored uncompressed in the APK, this can add a few megabytes. But Google Play uses its own compression (App Bundle format) during distribution, so users downloading from the store will see little to no difference in download size. The real win is at runtime — the OS can memory-map the uncompressed file directly, avoiding a copy into heap memory.
This matters a lot for apps targeting emerging markets. A 400ms improvement in time-to-interactive can genuinely be the difference between a user engaging with your app or bailing during the loading screen. If your analytics show a significant share of users on mid-range or budget Android devices, this optimization alone justifies the 0.79 upgrade.
Native Module Registration via package.json
This one's a quality-of-life win that's hard to overstate. Previously, registering native modules — especially on iOS — required boilerplate in your AppDelegate and was particularly painful when working with Swift.
The Old Way: Manual Registration
Before 0.79, registering a C++ TurboModule on iOS meant modifying your AppDelegate and writing manual registration code:
// AppDelegate.mm (Old approach)
#import "NativeCalculator.h"
@implementation AppDelegate
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const std::string &)name
jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker {
if (name == "NativeCalculator") {
return std::make_shared<facebook::react::NativeCalculator>(jsInvoker);
}
return nullptr;
}
@end
Yeah. Not exactly fun.
The New Way: package.json Configuration
With 0.79, you declare your module's registration directly in package.json using the modulesProvider field:
{
"name": "my-native-module",
"codegenConfig": {
"name": "MyNativeModule",
"type": "modules",
"jsSrcsDir": "src",
"ios": {
"modulesProvider": {
"NativeCalculator": "NativeCalculatorProvider"
}
}
}
}
The codegen system handles the rest, generating the necessary registration code at build time. This approach brings several wins:
- No AppDelegate modifications needed: Module registration is handled entirely by codegen
- Swift compatibility: Solves the limitation from 0.77 that prevented registering pure C++ Native Modules with a Swift AppDelegate
- Library authors benefit too: Libraries can specify the same properties in their
package.json, and codegen handles registration automatically - Way less boilerplate: Eliminates dozens of lines of registration code per module
JavaScriptCore Migration to Community Package
React Native 0.79 starts the process of moving JavaScriptCore (JSC) to a standalone, community-maintained package: @react-native-community/javascriptcore. The core-provided JSC still ships with 0.79, but this marks the beginning of its eventual removal from the core framework.
Why This Matters
For most apps using Hermes (the default engine since 0.70), this change is basically invisible. But for teams that still rely on JSC — whether for compatibility reasons, specific JS feature requirements, or debugging workflows — it has some important implications:
- Faster JSC updates: As a community package, JSC can receive updates independently of React Native releases
- Smaller core: Removing JSC from core reduces the framework's maintenance surface
- Opt-in usage: Teams that need JSC can explicitly add it; those that don't won't carry the extra weight
Opting Into Community JSC
If you need JSC instead of Hermes, install the community package:
# Install the community JSC package
npm install @react-native-community/javascriptcore
# or with yarn
yarn add @react-native-community/javascriptcore
Then follow the setup instructions from the package's README to configure it for your iOS and Android builds. For most projects though, sticking with Hermes is the way to go — it provides better startup performance and lower memory usage.
Remote JS Debugging Removed: What to Use Instead
React Native 0.79 officially removes Remote JS Debugging via Chrome. This legacy feature was deprecated back in 0.73, and honestly, it was kind of a mess. It worked by running your JavaScript in Chrome's V8 engine rather than on the device, which caused subtle behavioral differences and was a constant source of confusion during debugging.
Good riddance, frankly.
Modern Debugging Alternatives
Here's what to use instead:
1. React Native DevTools (Built-in)
This is the official debugging experience. It runs your code on the actual device engine (Hermes or JSC), supporting breakpoints, network inspection, component inspection, and performance profiling:
# React Native DevTools opens automatically when you press 'j'
# in the Metro terminal, or you can launch it manually:
npx react-native start
# Then press 'j' to open the debugger
2. Expo DevTools Plugins
For Expo projects, DevTools plugins give you an extensible debugging experience with custom inspectors, network monitoring, and state inspection:
# Expo DevTools are available through the Expo CLI
npx expo start
# DevTools URL is printed in the terminal output
3. Flipper (Community-maintained)
Meta has reduced its direct investment in Flipper, but it's still a solid option for advanced scenarios like database inspection, native module debugging, and custom plugins. The community continues to maintain and extend it.
The big advantage of all these modern alternatives over the old Remote JS Debugging is simple: they run your code on the actual device engine. No more phantom bugs that appear on the device but vanish in the debugger (or vice versa) because of behavioral differences between Chrome's V8 and on-device Hermes.
Breaking Changes: Complete Reference
React Native 0.79 does include several breaking changes you'll need to address before upgrading. Let's go through each one with the specific fix.
1. Unitless Lengths in Box Shadow and Filter
React Native now enforces CSS-compliant units in boxShadow and filter style properties. Unitless numeric values won't work anymore.
// BEFORE (broken in 0.79)
const styles = StyleSheet.create({
card: {
boxShadow: '1 1 5 black',
},
});
// AFTER (correct)
const styles = StyleSheet.create({
card: {
boxShadow: '1px 1px 5px black',
},
});
Audit your codebase for any boxShadow or filter values using unitless numbers and add explicit px units. It's a quick fix but easy to miss if you have styles scattered across many files.
2. HWB Color Function Syntax
The comma-separated syntax for hwb() is no longer supported. React Native now follows the CSS Color Level 4 spec, which uses space-separated values:
// BEFORE (broken in 0.79)
const styles = StyleSheet.create({
container: {
backgroundColor: 'hwb(0, 0%, 100%)', // Comma-separated
},
});
// AFTER (correct)
const styles = StyleSheet.create({
container: {
backgroundColor: 'hwb(0 0% 100%)', // Space-separated
},
});
This one probably affects fewer projects (most people aren't using HWB colors), but worth checking.
3. Deep Imports Require .default
Deep imports into React Native library modules using require() now need .default appended to access the module's default export:
// BEFORE (broken in 0.79)
const SomeComponent = require('react-native/Libraries/SomeComponent');
// AFTER (correct)
const SomeComponent = require('react-native/Libraries/SomeComponent').default;
// BEST (use ES module imports instead)
import SomeComponent from 'react-native/Libraries/SomeComponent';
The recommended approach is to just migrate from require() to ES module import syntax wherever possible, which sidesteps this issue entirely.
4. Remote JS Debugging Removed
As covered earlier, the Debug JS Remotely option is completely gone. If your workflow depended on Chrome-based debugging, switch to React Native DevTools or Expo DevTools.
Hermes Engine Improvements
The Hermes engine in React Native 0.79 gets several under-the-hood improvements that enhance both runtime performance and developer ergonomics.
React 19 Targeting and forwardRef Changes
The Hermes parser for Metro has been updated to target React 19. This means Component Syntax no longer produces forwardRef calls. In React 19, ref is passed as a regular prop rather than through a special forwardRef wrapper:
// React 18 pattern (still works but no longer generated by codegen)
const MyInput = React.forwardRef((props, ref) => {
return <TextInput ref={ref} {...props} />;
});
// React 19 pattern (now the default)
function MyInput({ ref, ...props }) {
return <TextInput ref={ref} {...props} />;
}
If your app uses Hermes (which it does by default), this transition happens automatically. But if you maintain component libraries, it's worth updating your components to use the new pattern for clarity.
Memory Management and Garbage Collection
Hermes's garbage collection has been tuned for better memory handling in long-running applications. This is particularly relevant for apps with large state trees, real-time data streams, or complex animations. The improvements reduce GC pause times, which translates to smoother UI interactions during memory-intensive operations.
Bytecode Compilation Optimizations
Bytecode compilation optimizations in the updated Hermes engine result in marginally faster JS execution across the board. Individual operations might only improve by small percentages, but the cumulative effect across an entire app's execution cycle adds up — especially on budget Android devices where CPU resources are tight.
Step-by-Step Migration Guide
Ready to upgrade? Here's the path from React Native 0.78 to 0.79.
Step 1: Check Your Dependencies
Before doing anything else, audit your dependencies for known compatibility issues with the package.json exports change:
# List all your dependencies
npm ls --depth=0
# Check for known issues with popular packages
npx react-native-doctor
Known packages that may need updates or workarounds include Firebase, AWS Amplify, and some older community packages that don't properly define their "exports" field.
Step 2: Update React Native
Use the React Native Upgrade Helper to see the exact file changes needed:
# Update React Native and related packages
npm install [email protected]
# Update the React Native CLI
npm install @react-native-community/cli@latest
# Update React (if not already on React 19)
npm install [email protected] [email protected]
Step 3: Update Native Projects
Apply the necessary changes to your native project files. The Upgrade Helper provides a detailed diff of what needs to change:
# For iOS, update pods
cd ios && pod install --repo-update && cd ..
# For Android, sync gradle
cd android && ./gradlew clean && cd ..
Step 4: Fix Breaking Changes
Run a search across your codebase for each breaking change:
# Search for unitless box shadows
grep -rn "boxShadow.*[0-9] [0-9]" src/
# Search for comma-separated hwb() usage
grep -rn "hwb(" src/
# Search for deep requires into react-native
grep -rn "require('react-native/" src/
# Search for remote debugging references
grep -rn "Debug.*Remote" src/
Step 5: Test Thoroughly
After upgrading, make sure to test these specific areas:
- Module resolution: Ensure all imports resolve correctly with the new exports/imports behavior
- Native modules: Verify that all native modules load and function properly
- Styling: Check all components using
boxShadoworfilterstyles - Android startup: Test on a real Android device to verify the improved startup time
- Debugging workflow: Confirm your debugging setup works without remote JS debugging
Upgrading with Expo
If you're using Expo, the upgrade is more streamlined. Expo SDK 53 supports React Native 0.79:
# Upgrade Expo SDK
npx expo install expo@^53.0.0
# Update all Expo packages to compatible versions
npx expo install --fix
# Check for any remaining issues
npx expo-doctor
Expo's managed workflow handles most of the native project changes automatically, but you should still check for the JS-level breaking changes (box shadows, HWB colors, deep imports).
Performance Benchmarks: Before and After
Let's look at some real numbers. Here are benchmarks across several key metrics to show the actual impact of 0.79's improvements.
Metro Startup Time
Measured on a monorepo with roughly 3,000 source files:
- React Native 0.78 (Metro 0.81): ~18 seconds to first dev server ready
- React Native 0.79 (Metro 0.82): ~6 seconds to first dev server ready
- Improvement: 3x faster
That's a 12-second improvement every time you start the dev server. It adds up fast.
Android App Startup (Samsung Galaxy A14)
- React Native 0.78 (compressed bundle): ~3,300ms time-to-interactive
- React Native 0.79 (uncompressed bundle): ~2,900ms time-to-interactive
- Improvement: ~400ms (12%) faster
CI Build Pipeline
Measured on a typical CI pipeline with Metro bundling, TypeScript checking, and test execution:
- React Native 0.78: Metro initialization contributed ~45 seconds to total pipeline time
- React Native 0.79: Metro initialization down to ~15 seconds
- Improvement: 30 seconds saved per CI run
If your team runs 50+ CI builds a day, that's 25 minutes of developer waiting time eliminated daily. Not transformative on its own, but it's the kind of thing that makes the whole workflow feel snappier.
Practical Tips for Getting the Most Out of 0.79
1. Leverage Package.json Exports in Your Own Libraries
Now that exports are stable, take the opportunity to add proper exports definitions to your internal packages:
{
"name": "@mycompany/ui-kit",
"exports": {
".": "./src/index.ts",
"./buttons": "./src/components/buttons/index.ts",
"./forms": "./src/components/forms/index.ts",
"./hooks": "./src/hooks/index.ts",
"./theme": "./src/theme/index.ts"
}
}
This gives you fine-grained control over what can be imported from your package and enables better tree-shaking.
2. Migrate Native Modules to Package.json Registration
If you've got custom native modules, now's the time to migrate them. Start with your simplest module to validate the approach, then work through the rest:
// 1. Define your TurboModule spec (src/NativeDeviceInfo.ts)
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
export interface Spec extends TurboModule {
getDeviceName(): Promise<string>;
getBatteryLevel(): Promise<number>;
getOSVersion(): string;
}
export default TurboModuleRegistry.getEnforcing<Spec>(
'NativeDeviceInfo'
);
// 2. Add codegen config to package.json
{
"codegenConfig": {
"name": "DeviceInfoSpec",
"type": "modules",
"jsSrcsDir": "src",
"ios": {
"modulesProvider": {
"NativeDeviceInfo": "DeviceInfoModuleProvider"
}
}
}
}
3. Profile Your Android Startup
With the new uncompressed bundle feature, it's a great time to profile your Android startup end-to-end and hunt down any remaining bottlenecks:
# Enable systrace for startup profiling
npx react-native start --reset-cache
# In another terminal, capture a systrace
adb shell atrace --async_start -b 8192 -c view res dalvik
# Launch your app and wait for it to be interactive
adb shell am start -n com.yourapp/.MainActivity
# Stop the trace after the app is interactive
adb shell atrace --async_stop -o /data/local/tmp/trace.txt
adb pull /data/local/tmp/trace.txt
4. Update Your Debugging Workflow
Since remote JS debugging is gone, make sure your entire team is set up with the modern tools:
// Add React Native DevTools to your development setup
// In your app's entry file, add performance marks for custom timing:
if (__DEV__) {
performance.mark('app-start');
}
// After your app is interactive:
if (__DEV__) {
performance.mark('app-interactive');
performance.measure(
'Time to Interactive',
'app-start',
'app-interactive'
);
}
Common Migration Issues and How to Solve Them
Based on community reports (and some trial and error on my end), here are the most common issues when upgrading to 0.79, along with solutions.
Issue 1: Firebase or AWS Amplify Import Errors
Some versions of Firebase and AWS Amplify haven't properly configured their "exports" field, causing Metro 0.82 to choke on subpath imports:
// Error: Unable to resolve module '@react-native-firebase/app/lib/common'
// from 'node_modules/@react-native-firebase/auth/lib/index.js'
// Fix: Add a resolution override in metro.config.js
const { getDefaultConfig } = require('@react-native/metro-config');
const config = getDefaultConfig(__dirname);
// Temporarily disable strict exports for problematic packages
config.resolver.unstable_conditionNames = ['require', 'react-native'];
module.exports = config;
Check for updated versions of these libraries first though — many have released patches addressing exports compatibility since 0.79 launched.
Issue 2: Native Module Not Found After Upgrade
If you see errors like "TurboModuleRegistry: Module not found" after upgrading, it usually means the module registration didn't survive the upgrade. Verify that your native modules are properly linked:
# Clear all caches and rebuild
npx react-native start --reset-cache
# For iOS, do a clean pod install
cd ios && rm -rf Pods Podfile.lock && pod install && cd ..
# For Android, clean and rebuild
cd android && ./gradlew clean && cd ..
Issue 3: Stylesheet Rendering Differences
Beyond the documented boxShadow changes, some developers have noticed subtle rendering differences in complex filter chains. If your app uses advanced CSS filters, test each one individually to isolate any behavioral changes. (Pro tip: creating a dedicated test screen that renders all your filtered components side-by-side can catch visual regressions fast.)
Issue 4: TypeScript Type Errors After React 19 Update
If your project uses TypeScript with React 19, you may hit type errors related to the ref prop changes. Update your @types/react package and adjust any custom component types:
# Update TypeScript types for React 19
npm install @types/react@^19.0.0 @types/react-dom@^19.0.0
# If using strict TypeScript, you may need to update
# component signatures that use forwardRef
Looking Ahead: What's Coming Next
React Native 0.79 builds on the momentum from the New Architecture going GA in 0.76. With over 83% of Expo SDK 54 projects now using the New Architecture, the ecosystem has firmly committed to the Fabric renderer, TurboModules, and JSI-based communication.
A few trends worth watching:
- React 19 integration deepening: As features like Server Components and Actions mature, expect deeper integration with React Native's rendering pipeline. The React team has signaled interest in bringing server-side rendering concepts to mobile — which could fundamentally change how data flows through React Native apps.
- Web convergence: The enforcement of CSS-compliant styling (box shadow units, HWB syntax) signals continued alignment with web standards. This makes it easier to share knowledge and code between React web and React Native, reinforcing the "learn once, write anywhere" vision.
- JSC sunset: The community JSC package is likely a stepping stone toward full removal from core. If you're still on JSC, start planning your Hermes migration. The performance benefits — particularly in startup time and memory efficiency — make it the clear choice.
- Codegen expansion: The package.json-based module registration will likely expand to cover more configuration that currently lives in native code. This "configuration over code" approach reduces boilerplate and makes native modules more accessible to JavaScript-first developers.
- Community-driven innovation: Moving JSC to a community package, combined with 944 commits from 100 contributors in this release alone, shows how healthy the React Native open-source ecosystem is. Expect more core features to get modularized in future releases.
Wrapping Up
React Native 0.79 is a release that genuinely rewards upgrading. The 3x Metro startup improvement alone makes daily development feel noticeably faster, and the 12% Android startup improvement translates directly to better UX for your end users.
The breaking changes are manageable and well-documented, and the migration path — especially through Expo SDK 53 — is smooth.
If you're still on 0.77 or 0.78, now's a great time to make the jump. Metro 0.82's deferred hashing, uncompressed Android bundles, simplified native module registration, and the continued maturation of the New Architecture make this one of the best React Native releases for both DX and end-user performance.
Start with the Upgrade Helper, fix the breaking changes in your styles and imports, and test thoroughly on both platforms. The investment in upgrading pays off immediately in faster dev cycles and happier users.
And for teams that have been putting off upgrading from earlier versions — each successive React Native version builds on the last. The longer you wait, the more breaking changes pile up. React Native 0.79 is stable, well-tested, and has strong ecosystem support through Expo SDK 53, making it an ideal target version for projects that have fallen behind.