React Compiler & New Architecture
Understanding the experimental features enabled in Caracal starter - React Compiler and New Architecture.
Caracal starter enables two experimental features that represent the future of React Native development. This guide explains what they are, how they affect your development, and when you might need to disable them.
Overview
The New Architecture is always on from Expo SDK 55 onward — there is no config flag for it. React Compiler is opted into in app.config.ts:
export default ({ config }: ConfigContext): ExpoConfig => ({
// ...
experiments: {
typedRoutes: true,
reactCompiler: true,
},
// ...
});Experimental Status: The New Architecture is mandatory and always enabled from Expo SDK 55 onward. React Compiler is stable enough for production use in Expo SDK 57 but is still marked as experimental, and its APIs may evolve.
React Compiler
What is React Compiler?
React Compiler (formerly "React Forget") is an automatic optimization tool that compiles your React components to eliminate the need for manual memoization.
Traditional React (without compiler):
import { useMemo, useCallback } from 'react';
function ExpensiveComponent({ data, onUpdate }) {
// Manual memoization required
const processedData = useMemo(() => {
return data.map(item => transformItem(item));
}, [data]);
const handleUpdate = useCallback((id) => {
onUpdate(id);
}, [onUpdate]);
return <View>{/* render */}</View>;
}
// Manual component memoization
export default React.memo(ExpensiveComponent);With React Compiler (enabled in Caracal):
// No manual memoization needed!
function ExpensiveComponent({ data, onUpdate }) {
// Compiler automatically optimizes this
const processedData = data.map(item => transformItem(item));
const handleUpdate = (id) => {
onUpdate(id);
};
return <View>{/* render */}</View>;
}
// No React.memo needed - compiler handles it
export default ExpensiveComponent;How It Works
The React Compiler analyzes your components during the build process and automatically:
- Memoizes expensive computations - Caches results of heavy calculations
- Prevents unnecessary re-renders - Skips rendering when props/state haven't changed
- Optimizes callbacks - Stabilizes function references automatically
- Eliminates manual optimization - No need for
useMemo,useCallback,React.memo
The result: Simpler code that's automatically optimized for performance.
What Changes for You
Write Simpler Code
// ✅ BEFORE React Compiler - Manual optimization
function UserProfile({ user, onSave }) {
const fullName = useMemo(() => {
return `${user.firstName} ${user.lastName}`;
}, [user.firstName, user.lastName]);
const handleSave = useCallback(() => {
onSave(user.id);
}, [onSave, user.id]);
return (
<View>
<Text>{fullName}</Text>
<Button onPress={handleSave}>Save</Button>
</View>
);
}
export default React.memo(UserProfile);// ✅ WITH React Compiler - Automatic optimization
function UserProfile({ user, onSave }) {
const fullName = `${user.firstName} ${user.lastName}`;
const handleSave = () => {
onSave(user.id);
};
return (
<View>
<Text>{fullName}</Text>
<Button onPress={handleSave}>Save</Button>
</View>
);
}
export default UserProfile;Trust the Compiler
- Don't pre-optimize: Write straightforward code, let the compiler optimize
- Remove manual memoization: No need for
useMemo/useCallback/React.memoin most cases - Keep code readable: Prioritize clarity over manual performance tricks
Performance Benefits
Measured improvements in production apps:
- 30-50% reduction in re-renders
- Faster component mount times
- Reduced memory usage from fewer function recreations
- Better scroll performance in lists
Limitations & Edge Cases
While React Compiler handles most cases, there are scenarios where it doesn't apply:
1. Dynamic Dependencies
// ❌ Compiler can't optimize this
function DynamicComponent({ fields }) {
// deps array is dynamic - compiler can't determine dependencies
const result = useMemo(() => {
return fields.reduce((acc, field) => acc + field, 0);
}, fields); // Array spread - compiler skips optimization
return <Text>{result}</Text>;
}Solution: Keep dependencies explicit and static when possible.
2. External Libraries
Third-party libraries not compiled with React Compiler won't benefit:
// ❌ External library might not be optimized
import SomeLibraryComponent from 'some-library';
// You still benefit from compiler in your code
function YourComponent() {
// This is optimized by compiler
const data = computeData();
// But SomeLibraryComponent isn't
return <SomeLibraryComponent data={data} />;
}3. Code with Side Effects
// ⚠️ Compiler is cautious with side effects
function ComponentWithSideEffects() {
// Compiler won't optimize this aggressively
// because of the console.log side effect
const value = (() => {
console.log('Computing...');
return computeExpensiveValue();
})();
return <Text>{value}</Text>;
}When to Still Use Manual Memoization
Keep useMemo/useCallback in these specific cases:
1. Extremely Expensive Operations
function DataProcessor({ dataset }) {
// Keep useMemo for operations taking >100ms
const processedData = useMemo(() => {
return dataset.map(item => {
// Complex processing taking 200ms+
return expensiveTransform(item);
});
}, [dataset]);
return <DataView data={processedData} />;
}2. After Profiling Shows Issues
// Profile first, then optimize if needed
function OptimizedAfterProfiling({ data }) {
// Added useMemo after React DevTools Profiler
// showed this was causing performance issues
const sortedData = useMemo(() => {
return [...data].sort(customSort);
}, [data]);
return <List data={sortedData} />;
}Debugging React Compiler
Disable for Specific Files
If a component has issues, opt-out with a directive:
// @react-compiler-disable
// Compiler will skip this file
function ProblematicComponent() {
// Your code here
}Common Issues
Problem: Component not updating when it should
Cause: Compiler over-optimized based on incorrect assumptions
Solution:
// Force re-render with key prop
<ComponentNotUpdating key={forceUpdateKey} />
// Or disable compiler for that componentNew Architecture
What is New Architecture?
React Native's New Architecture (also called "Fabric" + "TurboModules") is a complete rewrite of React Native's core rendering and native module systems.
Key Changes:
- Fabric: New rendering engine replacing the old "Bridge"
- TurboModules: Faster native module system with lazy loading
- JSI (JavaScript Interface): Direct JavaScript-to-native communication
- Codegen: Automatic type-safe bindings between JS and native code
Why It Matters
Old Architecture (Bridge-Based)
JavaScript Bridge (Async) Native
| | |
|--- Send JSON message ----->| |
| |--- Parse & forward ---->|
| | |
|<--- Send JSON response ----|<--- Process & return ---|
| | |Problems:
- Everything async (even simple getters)
- JSON serialization overhead
- No direct memory access
- Performance bottlenecks
New Architecture (JSI-Based)
JavaScript JSI (Sync) Native
| | |
|--- Direct function call -->|--- Immediate access --->|
|<--- Return value ----------|<--- Return directly ----|
| | |Benefits:
- Synchronous access to native modules
- No JSON serialization
- Direct memory sharing (ArrayBuffers)
- Better performance
Performance Improvements
Measured in production apps with New Architecture:
| Metric | Improvement |
|---|---|
| App Launch Time | 20-30% faster |
| Frame Rate | More consistent 60fps |
| Memory Usage | 10-15% reduction |
| Native Module Calls | 2-3x faster |
| Large List Scrolling | Smoother, fewer drops |
Library Compatibility
Most popular libraries support New Architecture:
Fully Compatible:
- React Navigation
- React Native Reanimated
- React Native Gesture Handler
- React Native Screens
- React Native Safe Area Context
- React Native MMKV
- Expo SDK (all modules)
- HeroUI Native
- Uniwind
Partial Support:
- Some older native modules may need updates
- Check React Native Directory for library status
Not Compatible:
- Very old libraries (pre-2020) without updates
- Libraries using old native module system without migration
Troubleshooting New Architecture
Issue: App crashes on startup
Possible cause: Incompatible native module
Solution:
- Check all dependencies for New Architecture support
- Update libraries to latest versions
- Remove incompatible libraries temporarily
Issue: Native module not found
Possible cause: Module needs rebuilding
Solution:
# iOS
cd ios && pod deintegrate && pod install && cd ..
# Android
cd android && ./gradlew clean && cd ..
# Rebuild
pnpm prebuildWhen to Disable These Features
Disable React Compiler If:
-
Debugging optimization issues
// app.config.ts experiments: { reactCompiler: false, // Temporarily disable } -
Specific components have problems
// Add to top of file // @react-compiler-disable
You Cannot Disable the New Architecture
The legacy architecture was removed in Expo SDK 55, and the newArchEnabled option no longer exists. Caracal runs on Expo SDK 57 / React Native 0.86, so the New Architecture is always active. If a library does not support it, replace the library rather than trying to opt out — see Library Compatibility.
Disabling React Compiler
1. Update app.config.ts:
export default ({ config }: ConfigContext): ExpoConfig => ({
// ...
experiments: {
typedRoutes: true,
reactCompiler: false, // Disable React Compiler
},
// ...
});2. Clear build caches:
rm -rf node_modules
rm -rf ios/build android/app/build
rm -rf .expo
pnpm install
pnpm prebuild
pnpm start --clear3. Rebuild app:
pnpm ios
pnpm androidBest Practices
With React Compiler
DO:
- Write simple, readable code
- Trust the compiler to optimize
- Profile before manually optimizing
- Remove unnecessary memoization
- Keep dependencies explicit
DON'T:
- Don't over-optimize prematurely
- Don't assume manual memoization is always better
- Don't skip testing after removing memoization
- Don't forget to profile in production mode
With New Architecture
DO:
- Check library compatibility before installing
- Update libraries regularly
- Test thoroughly on both platforms
- Use Expo modules when possible (built for New Architecture)
- Monitor crash reports for native issues
DON'T:
- Don't assume all libraries work immediately
- Don't skip testing after library updates
- Don't ignore deprecation warnings
- Don't mix old and new native module patterns
Verify Features Are Active
Verify React Compiler is Active
# Build in production mode
pnpm prebuild:production
# Check build output for compiler messages
# Look for: "Compiled N components with React Compiler"Verify New Architecture is Active
// Add to a screen temporarily
import { Text, View } from 'react-native';
export default function DebugScreen() {
const isFabric = global.nativeFabricUIManager != null;
return (
<View>
<Text>New Architecture: {isFabric ? 'Enabled' : 'Disabled'}</Text>
</View>
);
}Or check native logs:
# iOS logs
# Look for: "Fabric enabled: true"
# Android logs
adb logcat | grep -i fabric
# Look for: "Fabric is enabled"Migration Path
If you're upgrading from an older starter version without these features:
Phase 1: Move to the New Architecture First
- Upgrade to Expo SDK 55 or later — the New Architecture is enabled automatically and cannot be turned off
- Update all dependencies to latest versions
- Test thoroughly on both platforms
- Fix any compatibility issues
Phase 2: Enable React Compiler
- Update
app.config.ts:reactCompiler: true - Remove unnecessary
useMemo/useCallback/React.memogradually - Test each change
- Profile to verify improvements
Phase 3: Optimize
- Profile app with both features enabled
- Identify remaining bottlenecks
- Add manual optimizations only where profiling shows need
- Monitor production performance
Resources
- React Compiler: https://react.dev/learn/react-compiler
- New Architecture: https://reactnative.dev/docs/new-architecture-intro
- Expo New Architecture: https://docs.expo.dev/guides/new-architecture/
- React Native Directory: https://reactnative.directory/ - Check library compatibility
FAQ
Q: Should I remove all useMemo/useCallback from my code?
A: No, not immediately. Remove them gradually:
- Profile first to understand current performance
- Remove memoization from simple components
- Keep memoization for extremely expensive operations (>100ms)
- Test and profile after each change
Q: Will React Compiler make my app slower?
A: No. React Compiler only adds optimizations, never removes them. If it can't optimize safely, it leaves code unchanged.
Q: Can I use New Architecture with Expo Go?
A: Partially. Expo Go in Expo SDK 57 runs on the New Architecture, but you need a custom dev client for full support with native modules. Use pnpm prebuild and pnpm ios/pnpm android.
Q: What if a critical library doesn't support New Architecture?
A: Opting out is not an option on SDK 55+, so you have to move the library forward:
- Find an alternative library that supports it
- Wait for the library to update (check GitHub issues)
- Contribute a PR to add support
- Pin to an older SDK only as a last resort, and plan the migration
Q: Can I gradually adopt React Compiler per-file?
A: The compiler is enabled globally, but you can opt-out specific files with // @react-compiler-disable directive at the top of the file.
Summary
Caracal starter enables React Compiler and New Architecture to provide:
- Automatic performance optimization without manual memoization
- Faster native module access and better threading
- Improved app startup time and frame rates
- Simpler code that's easier to maintain
- Future-proof architecture aligned with React Native's direction
Bottom line: These features make your app faster and your code simpler. Start building with confidence, and only disable them if you encounter specific compatibility issues with third-party libraries.
Next Steps:
- Project Structure - Understand the codebase organization
- Rules and Conventions - Code standards for the React Compiler era