The Ultimate Guide to React Native Performance Optimization in 2026

A practical guide to React Native performance in 2026 — covering the New Architecture, Hermes V1, React Compiler, FlashList, image optimization, bundle reduction, and profiling tools with real code examples.

Why Performance Matters More Than Ever in React Native

React Native has come a long way in the past few years, and honestly, 2026 feels like a turning point for mobile performance. The New Architecture is enabled by default now, Hermes V1 is on the horizon, and the React Compiler is automating optimizations that we used to handle manually. The performance landscape has fundamentally shifted.

But here's the thing — the gap between a sluggish app and a buttery-smooth 60 FPS experience still comes down to the choices you make as a developer.

This guide is a practical walkthrough of every major performance optimization technique available to React Native developers in 2026. Whether you're building a new app from scratch or trying to squeeze more speed out of an existing one, you'll find actionable strategies backed by real benchmarks and production-tested patterns. We'll cover the full stack — from architecture-level decisions down to individual component tweaks — so you can ship apps that feel truly native.

The New Architecture: Your Performance Foundation

The single biggest performance lever in React Native today is the New Architecture. If you're still running on the old bridge-based system, migrating should be your top priority. As of early 2026, roughly 83% of Expo SDK 54 projects built with EAS Build already use the New Architecture — and for good reason.

Understanding the Performance Gains

The old architecture relied on a JSON-serialized bridge to communicate between JavaScript and native code. Every interaction — layout calculations, native module calls, event handling — had to be serialized, sent across the bridge, deserialized, and processed. It was a lot of overhead, to put it mildly.

The New Architecture eliminates this bottleneck through three key technologies:

  • JSI (JavaScript Interface): JavaScript can now hold direct references to C++ host objects. Your JS code talks to native modules almost instantly, without any JSON serialization overhead.
  • Fabric: The new rendering system supports concurrent rendering, synchronous layout measurements, and priority-based updates. Components can render on the main thread when needed for responsive interactions.
  • TurboModules: Native modules load lazily on demand instead of all at startup, and they communicate through JSI rather than the bridge. This alone can cut startup time significantly.

The combined effect is pretty transformative. Apps running the New Architecture typically see a 30–50% reduction in Time-to-Interactive (TTI), smoother animations, and more responsive touch handling. Bridgeless mode takes it even further by eliminating all bridge initialization — error handling, timers, and global event emitters all run natively through JSI.

Enabling the New Architecture

If you're using Expo SDK 54 or later, the New Architecture is enabled by default. For bare React Native projects on 0.79+, enable it in your configuration:

// app.json (Expo)
{
  "expo": {
    "newArchEnabled": true
  }
}

// For bare React Native on Android (gradle.properties)
newArchEnabled=true

// For bare React Native on iOS (Podfile)
ENV['RCT_NEW_ARCH_ENABLED'] = '1'

After enabling, rebuild your app completely. Run npx react-native-new-arch-helper check to verify that all your dependencies support the New Architecture. Libraries that haven't migrated to TurboModules will fall back to an interop layer, which works but doesn't deliver the full performance benefits.

Hermes V1: The Next-Generation JavaScript Engine

Hermes has been the default JavaScript engine for React Native since version 0.70, but Hermes V1 — introduced experimentally in React Native 0.82 — is a generational leap. If you're on React Native 0.82 or later, you can opt into it today.

What Hermes V1 Brings

Hermes V1 delivers improved VM performance across the board. You get better support for modern JavaScript features including proper ES6 classes, const and let scoping, and enhanced async/await handling. The engine is faster at parsing, compiling, and executing JavaScript, which translates directly into faster startup and smoother runtime performance.

The roadmap is even more exciting, honestly. Future releases plan to introduce JIT (Just-In-Time) compilation, the ability to compile JavaScript bundles to native binaries, and type-informed optimizations that leverage TypeScript annotations for faster execution. These features aren't available yet, but adopting Hermes V1 now positions your app to benefit from them the moment they land.

Verifying Hermes Is Active

You can check that Hermes is running with a simple runtime check:

// Check Hermes status at runtime
const isHermes = () => {
  return typeof HermesInternal !== 'undefined';
};

console.log(`Running on Hermes: ${isHermes()}`);

// Access Hermes-specific performance info
if (isHermes()) {
  const heapInfo = HermesInternal?.getInstrumentedStats?.();
  console.log('Heap size:', heapInfo?.js_heapSize);
}

Expo projects using SDK 54+ have Hermes enabled by default. For bare projects, verify your build.gradle includes hermesEnabled: true and your Podfile doesn't override the Hermes setting.

React Compiler: Automatic Memoization in Production

The React Compiler hit version 1.0 in late 2025, and it's one of the most impactful performance tools for React Native developers right now. It's a build-time optimization tool that automatically memoizes your components and hooks — eliminating the need for manual React.memo, useMemo, and useCallback in most cases.

How the Compiler Optimizes Your Code

The React Compiler works as a Babel plugin that analyzes your component code at build time. It lowers your code into a novel High-level Intermediate Representation (HIR) and performs multiple compiler passes to understand data flow and mutability patterns. Based on this analysis, it granularly memoizes values used in rendering — including conditional memoization, which is basically impossible to do well with manual approaches.

In production at Meta, the React Compiler has demonstrated up to 12% faster page loads and 2.5x quicker user interactions. These aren't synthetic benchmarks — they come from apps serving billions of users.

Enabling the React Compiler

Expo SDK 54 and later enables the React Compiler by default. For other setups, install and configure it manually:

npm install -D babel-plugin-react-compiler

// babel.config.js
module.exports = {
  plugins: [
    ['babel-plugin-react-compiler', {
      // Target React Native runtime
      target: '19', // React 19+
    }],
  ],
};

Writing Compiler-Friendly Code

While the compiler handles most optimizations automatically, certain patterns help it generate better output. So, let's look at what works well and what doesn't:

// Good: Pure component logic the compiler can optimize
function ProductCard({ product, onPress }) {
  const formattedPrice = `$${product.price.toFixed(2)}`;
  const inStock = product.inventory > 0;

  return (
    <Pressable onPress={() => onPress(product.id)}>
      <Text>{product.name}</Text>
      <Text>{formattedPrice}</Text>
      {inStock ? <Badge text="In Stock" /> : <Badge text="Sold Out" />}
    </Pressable>
  );
}

// Avoid: Side effects during render confuse the compiler
function BadComponent({ data }) {
  // Don't mutate external variables during render
  globalCounter++; // The compiler cannot safely memoize this

  // Don't read from mutable refs during render for display
  const val = someRef.current; // Unstable dependency

  return <Text>{val}</Text>;
}

You don't need to remove existing useMemo and useCallback calls — the compiler respects them. But for new code, just write straightforward components and let the compiler do its thing.

List Performance: FlashList, Legend List, and Beyond

Lists are the most common performance bottleneck in mobile apps. A feed, a product catalog, a chat view — if your list stutters during scrolling, users notice immediately. I've personally spent more debugging hours on list performance than almost any other issue in React Native.

React Native's built-in FlatList works for simple cases, but for production apps with hundreds or thousands of items, you need a better solution.

FlashList: The Production Standard

FlashList, created by Shopify, replaces FlatList's virtualization strategy with cell recycling. Instead of destroying and recreating components as they scroll off-screen, FlashList maintains a fixed pool of component instances and swaps their data. This dramatically reduces garbage collection pressure and JavaScript thread work.

The real-world benchmarks tell the story: teams switching from FlatList to FlashList consistently report 54% FPS improvements (jumping from ~37 FPS to ~57 FPS), 82% CPU reduction on the JS thread, and elimination of out-of-memory crashes on low-end Android devices. FlashList v2, rebuilt for the New Architecture, eliminates the need for item size estimates entirely.

import { FlashList } from "@shopify/flash-list";

function ProductFeed({ products }) {
  const renderItem = useCallback(({ item }) => (
    <ProductCard product={item} />
  ), []);

  return (
    <FlashList
      data={products}
      renderItem={renderItem}
      estimatedItemSize={120}
      keyExtractor={(item) => item.id}
      // Optimize for specific layout
      numColumns={2}
      // Draw distance controls how far ahead items render
      drawDistance={250}
    />
  );
}

Legend List: The New Contender

Legend List from LegendApp has emerged as a strong alternative — particularly for complex UI scenarios. Benchmarks show it uses less CPU and memory than FlashList during scrolling, making it especially attractive for media-heavy feeds, real-time updating lists, and layouts with dynamically sized items.

import { LegendList } from "@legendapp/list";

function ChatMessages({ messages }) {
  return (
    <LegendList
      data={messages}
      renderItem={({ item }) => <ChatBubble message={item} />}
      keyExtractor={(item) => item.id}
      // Legend List handles dynamic heights natively
      estimatedItemSize={80}
      // Inverted for chat-style bottom-to-top scrolling
      inverted
      // Recycle items for performance
      recycleItems
    />
  );
}

Choosing the Right List Component

Here's a practical decision framework:

  • FlatList: Short lists (under 100 items) with simple layouts. No extra dependency needed.
  • FlashList: Medium to large lists where you need proven stability, great documentation, and broad community support. Excellent for e-commerce catalogs, social feeds, and standard data lists.
  • Legend List: Complex UIs with multiple nested lists, media-heavy content, real-time updates, or dynamically sized items where you need maximum performance with minimal config.

Universal List Optimization Tips

Regardless of which list component you choose, these optimizations apply across the board:

// 1. Stabilize renderItem with useCallback
const renderItem = useCallback(({ item }) => (
  <MemoizedListItem item={item} />
), []);

// 2. Use getItemLayout when items have fixed heights
const getItemLayout = useCallback((data, index) => ({
  length: ITEM_HEIGHT,
  offset: ITEM_HEIGHT * index,
  index,
}), []);

// 3. Optimize the keyExtractor
const keyExtractor = useCallback((item) => item.id.toString(), []);

// 4. Reduce re-renders with stable references
// Bad: creates a new object every render
<FlatList contentContainerStyle={{ padding: 16 }} />

// Good: stable style reference
const styles = StyleSheet.create({
  container: { padding: 16 },
});
<FlatList contentContainerStyle={styles.container} />

Image Optimization: Fast Loading Without the Memory Cost

Images are typically the largest assets your app loads, and poorly optimized image handling is one of the most common causes of memory issues and slow perceived performance. In 2026, two libraries dominate the React Native image landscape.

expo-image: The Modern Standard

If you're using Expo (and at this point, you probably should be), expo-image is the recommended solution. It provides aggressive caching, automatic resizing to container dimensions, blur hash placeholders, and support for modern formats like WebP and AVIF.

The automatic resizing is particularly valuable — it reduces memory usage by ensuring the decoded image in memory matches the display size rather than the source size. That might sound like a small thing, but when you're rendering a 4000x3000 photo in a 200x200 thumbnail, the memory difference is enormous.

import { Image } from 'expo-image';

function OptimizedProductImage({ uri, blurhash }) {
  return (
    <Image
      source={{ uri }}
      // Blur hash shows instantly while image loads
      placeholder={{ blurhash }}
      // Smooth transition from placeholder
      transition={300}
      // Resize to container, saving memory
      contentFit="cover"
      // Cache aggressively
      cachePolicy="memory-disk"
      // Enable recycling in lists
      recyclingKey={uri}
      style={{ width: '100%', aspectRatio: 1 }}
    />
  );
}

Image Optimization Best Practices

Beyond choosing the right library, follow these practices for optimal image performance:

  • Use WebP format: WebP images are 25–35% smaller than equivalent JPEG or PNG files with no visible quality loss. Most CDNs can convert images to WebP on the fly.
  • Serve correctly sized images: If your image displays at 200x200 pixels on a 3x density screen, request a 600x600 image from your CDN — not the original 4000x3000 photo.
  • Prefetch strategically: In lists, prefetch images for items just beyond the visible area. Both expo-image and react-native-fast-image support prefetching APIs.
  • Use priority loading: Assign high priority to images in the initial viewport (hero images, profile avatars) and normal or low priority to off-screen content.
import { Image } from 'expo-image';

// Prefetch upcoming images in a list
async function prefetchNextPage(imageUrls) {
  await Promise.all(
    imageUrls.map(url => Image.prefetch(url))
  );
}

// In your list component
function ImageFeed({ pages, currentPage }) {
  useEffect(() => {
    const nextPage = pages[currentPage + 1];
    if (nextPage) {
      prefetchNextPage(nextPage.map(item => item.imageUrl));
    }
  }, [currentPage]);

  // ... render list
}

Bundle Size: Shipping Less JavaScript

Every kilobyte of JavaScript your app ships must be parsed, compiled, and executed before your app becomes interactive. Reducing bundle size directly improves startup time, and in 2026 there are some powerful tools to help.

Analyzing Your Bundle

Before optimizing, measure. Use react-native-bundle-visualizer to create a visual map of your bundle composition:

npx react-native-bundle-visualizer

# For Expo projects
npx expo export --dump-sourcemap
npx source-map-explorer dist/bundles/ios-*.js

A typical finding (and I've seen this over and over): 30–40% of your bundle comes from just a few large dependencies. Identifying and addressing these outliers delivers outsized improvements.

Practical Bundle Reduction Strategies

Start with the highest-impact changes:

  • Audit your dependencies: Run npx depcheck to find unused packages. It's surprisingly common to find libraries imported during development that never made it to production code.
  • Choose lighter alternatives: Replace moment.js (~300KB) with date-fns (~20KB for typical usage) or the native Intl.DateTimeFormat API. Replace lodash with individual function imports or native methods.
  • Tree-shake aggressively: Use named imports (import { map } from 'lodash-es') instead of default imports (import _ from 'lodash') to enable tree shaking.
  • Lazy-load heavy screens: Screens the user doesn't see immediately should load on demand.
import { lazy, Suspense } from 'react';
import { ActivityIndicator } from 'react-native';

// Lazy load heavy screens
const AnalyticsDashboard = lazy(
  () => import('./screens/AnalyticsDashboard')
);
const SettingsScreen = lazy(
  () => import('./screens/SettingsScreen')
);

// Use Suspense for loading states
function AppNavigator() {
  return (
    <Stack.Navigator>
      <Stack.Screen name="Home" component={HomeScreen} />
      <Stack.Screen name="Analytics">
        {() => (
          <Suspense fallback={<ActivityIndicator size="large" />}>
            <AnalyticsDashboard />
          </Suspense>
        )}
      </Stack.Screen>
    </Stack.Navigator>
  );
}

Metro Bundler Optimizations

Metro, React Native's bundler, has some configuration options that can shave real kilobytes off your bundle:

// metro.config.js
const { getDefaultConfig } = require('expo/metro-config');

const config = getDefaultConfig(__dirname);

// Enable minification optimizations
config.transformer.minifierConfig = {
  compress: {
    drop_console: true,  // Remove console.log in production
    drop_debugger: true,  // Remove debugger statements
    pure_getters: true,
    passes: 2,  // Multiple optimization passes
  },
  mangle: {
    toplevel: true,
  },
};

module.exports = config;

Rendering Performance: Avoiding Unnecessary Re-renders

Even with the React Compiler handling memoization, understanding re-render patterns helps you write components that are inherently efficient. The compiler optimizes what you give it — better input produces better output.

Component Architecture for Performance

The most effective re-render optimization is structural: isolate state to the smallest possible component tree. This is one of those things that sounds obvious but makes a huge difference in practice.

// Bad: Entire screen re-renders when timer ticks
function ProductScreen({ product }) {
  const [timeLeft, setTimeLeft] = useState(calculateTimeLeft());

  useEffect(() => {
    const timer = setInterval(() => {
      setTimeLeft(calculateTimeLeft());
    }, 1000);
    return () => clearInterval(timer);
  }, []);

  return (
    <ScrollView>
      <ProductImage uri={product.imageUrl} />
      <ProductDetails product={product} />
      <Text>Sale ends in: {timeLeft}</Text>  {/* Only this changes */}
      <RelatedProducts categoryId={product.categoryId} />
    </ScrollView>
  );
}

// Good: Only the countdown re-renders
function ProductScreen({ product }) {
  return (
    <ScrollView>
      <ProductImage uri={product.imageUrl} />
      <ProductDetails product={product} />
      <CountdownTimer />  {/* Isolated state */}
      <RelatedProducts categoryId={product.categoryId} />
    </ScrollView>
  );
}

function CountdownTimer() {
  const [timeLeft, setTimeLeft] = useState(calculateTimeLeft());

  useEffect(() => {
    const timer = setInterval(() => {
      setTimeLeft(calculateTimeLeft());
    }, 1000);
    return () => clearInterval(timer);
  }, []);

  return <Text>Sale ends in: {timeLeft}</Text>;
}

Expensive Computations

When you have genuinely expensive computations that the compiler might not optimize perfectly, explicit memoization is still the way to go:

function SearchResults({ query, products }) {
  // Expensive: filtering and sorting thousands of items
  const filteredProducts = useMemo(() => {
    return products
      .filter(p => p.name.toLowerCase().includes(query.toLowerCase()))
      .sort((a, b) => b.relevanceScore - a.relevanceScore)
      .slice(0, 100);
  }, [query, products]);

  return <FlashList data={filteredProducts} /* ... */ />;
}

Animation Performance: Staying on the UI Thread

Animations that run on the JavaScript thread compete with your business logic, network requests, and state management for processing time. The result? Dropped frames and janky transitions. Nobody wants that.

The solution is to keep animations on the UI thread.

Reanimated for UI-Thread Animations

React Native Reanimated runs animations directly on the UI thread using worklets — small JavaScript functions that execute outside the main JS context. Combined with Reanimated 4's CSS Animations support, you've got multiple approaches depending on complexity:

import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withSpring,
  withTiming,
} from 'react-native-reanimated';

function AnimatedCard({ isExpanded }) {
  const height = useSharedValue(80);

  useEffect(() => {
    height.value = withSpring(isExpanded ? 200 : 80, {
      damping: 15,
      stiffness: 150,
    });
  }, [isExpanded]);

  const animatedStyle = useAnimatedStyle(() => ({
    height: height.value,
    overflow: 'hidden',
  }));

  return (
    <Animated.View style={animatedStyle}>
      {/* Card content */}
    </Animated.View>
  );
}

For simpler transitions, Reanimated 4's CSS-style API is even cleaner and performs identically (since it also runs on the UI thread):

import Animated from 'react-native-reanimated';

// CSS Transitions — declare once, animate automatically
const animatedStyle = {
  height: isExpanded ? 200 : 80,
  transitionProperty: 'height',
  transitionDuration: '300ms',
  transitionTimingFunction: 'ease-in-out',
};

<Animated.View style={animatedStyle} />

Gesture-Driven Animations

For gesture-driven animations (swipeable cards, pull-to-refresh, pinch-to-zoom), always pair react-native-gesture-handler with Reanimated to keep the entire gesture-to-animation pipeline on the UI thread:

import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withSpring,
} from 'react-native-reanimated';

function SwipeableCard() {
  const translateX = useSharedValue(0);

  const panGesture = Gesture.Pan()
    .onUpdate((event) => {
      translateX.value = event.translationX;
    })
    .onEnd(() => {
      translateX.value = withSpring(0);
    });

  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{ translateX: translateX.value }],
  }));

  return (
    <GestureDetector gesture={panGesture}>
      <Animated.View style={animatedStyle}>
        {/* Card content */}
      </Animated.View>
    </GestureDetector>
  );
}

Memory Management: Preventing Leaks Before They Start

Memory leaks in React Native are insidious. They don't throw errors, don't show warnings, and won't crash your app immediately. Instead, they slowly degrade performance until the OS eventually kills your app. Fun times.

The best approach is prevention through architecture rather than detection through debugging.

Common Memory Leak Patterns

These are the most frequent causes of memory leaks I've encountered in React Native apps:

// Leak: Timer not cleaned up
function LeakyComponent() {
  useEffect(() => {
    const interval = setInterval(fetchData, 5000);
    // Missing: return () => clearInterval(interval);
  }, []);
}

// Leak: Event listener not removed
function LeakyListener() {
  useEffect(() => {
    const subscription = AppState.addEventListener('change', handler);
    // Missing: return () => subscription.remove();
  }, []);
}

// Leak: Setting state on unmounted component
function LeakyAsync() {
  const [data, setData] = useState(null);

  useEffect(() => {
    let cancelled = false;

    fetchData().then(result => {
      if (!cancelled) {
        setData(result); // Safe: only set if still mounted
      }
    });

    return () => { cancelled = true; };
  }, []);
}

Production Memory Monitoring

Use Hermes's built-in memory tracking to monitor heap usage in development:

// Monitor memory in development
function useMemoryMonitor(screenName) {
  useEffect(() => {
    if (__DEV__ && typeof HermesInternal !== 'undefined') {
      const before = HermesInternal?.getInstrumentedStats?.();

      return () => {
        // Force garbage collection
        HermesInternal?.collectGarbage?.();

        const after = HermesInternal?.getInstrumentedStats?.();
        const heapGrowth = (after?.js_heapSize || 0) - (before?.js_heapSize || 0);

        if (heapGrowth > 1_000_000) { // More than 1MB growth
          console.warn(
            `[Memory] ${screenName} may have a leak: ` +
            `heap grew by ${(heapGrowth / 1_000_000).toFixed(2)}MB`
          );
        }
      };
    }
  }, [screenName]);
}

Profiling and Measuring Performance

You can't optimize what you don't measure. React Native provides several tools for profiling performance, and using them should be a regular part of your development workflow — not just when something feels slow.

The React Native Performance Monitor

The simplest starting point is the built-in performance monitor. On a dev build, shake your device (or press Cmd+D in the simulator) and enable "Show Perf Monitor." This overlay shows real-time FPS for both the UI thread and JS thread. Your target: both threads should stay at or near 60 FPS during interactions.

React DevTools Profiler

The React DevTools Profiler shows exactly which components rendered and why. Connect React DevTools to your app and record a profiling session during the interaction you want to optimize:

# Start React DevTools
npx react-devtools

# Or use the built-in Expo Dev Tools
npx expo start --dev-client

Look for components that render repeatedly without their props changing — those are your prime candidates for state isolation or memoization. The "Why did this render?" feature in the Profiler settings is particularly useful for tracking down unnecessary re-renders.

Flipper and Platform-Specific Profiling

For deeper analysis, reach for platform-specific tools:

  • Flipper: Meta's debugging platform includes a performance plugin that visualizes JS thread and UI thread activity on a timeline. It's excellent for identifying bridge calls, layout thrashing, and long-running JavaScript operations.
  • Xcode Instruments: For iOS-specific profiling, use the Time Profiler and Allocations instruments. These show native-level performance data that JavaScript tools simply can't capture — including Core Animation rendering, memory allocations, and energy impact.
  • Android Studio Profiler: For Android, the built-in profiler provides CPU, memory, network, and energy profiling. The CPU profiler is particularly useful for identifying native thread contention.

Automated Performance Testing

The most effective performance strategy is catching regressions before they ship. Integrate performance benchmarks into your CI pipeline:

// performance.test.js — Using Reassure for performance regression testing
import { measurePerformance } from 'reassure';
import { ProductList } from '../components/ProductList';

test('ProductList renders 1000 items efficiently', async () => {
  const products = generateMockProducts(1000);

  await measurePerformance(
    <ProductList products={products} />,
    { runs: 10 }
  );
});

test('ProductCard does not re-render without prop changes', async () => {
  const product = generateMockProduct();

  await measurePerformance(
    <ProductCard product={product} />,
    {
      runs: 10,
      scenario: async (screen) => {
        // Trigger parent re-render without changing this component's props
        await screen.rerender(<ProductCard product={product} />);
      },
    }
  );
});

Reassure, built by Callstack, runs render performance tests and compares results against your baseline. If a component's render time regresses beyond a configurable threshold, CI fails. It's a game-changer for teams that care about shipping fast apps consistently.

Startup Optimization: First Impressions Matter

App startup time is the first performance metric your users experience. A slow launch creates an immediate negative impression, regardless of how smooth the app runs afterward. You don't get a second chance at a first impression (cliche but true).

Measuring Startup Time

Define and track specific startup milestones:

// Track startup milestones
const startupMetrics = {
  nativeInit: Date.now(), // Set in native code
  jsInit: 0,
  firstRender: 0,
  interactive: 0,
};

// In your root component
function App() {
  useEffect(() => {
    startupMetrics.firstRender = Date.now();

    // Mark as interactive when initial data is loaded
    InteractionManager.runAfterInteractions(() => {
      startupMetrics.interactive = Date.now();

      const tti = startupMetrics.interactive - startupMetrics.nativeInit;
      console.log(`Time to Interactive: ${tti}ms`);
    });
  }, []);
}

Proven Startup Optimizations

  • Defer non-critical work: Use InteractionManager.runAfterInteractions to schedule analytics initialization, background syncs, and other non-visible work after the first screen is interactive.
  • Optimize your splash screen: Use expo-splash-screen to keep the native splash screen visible until your app is truly ready. This hides the JavaScript initialization time behind a polished native experience.
  • Minimize root component complexity: Your root component and the first visible screen should import as few dependencies as possible. Heavy screens deeper in the navigation stack should be lazy-loaded.
  • Preload critical assets: Use Asset.loadAsync and Font.loadAsync during the splash screen phase to prevent layout shifts after the app renders.
import * as SplashScreen from 'expo-splash-screen';
import * as Font from 'expo-font';
import { Asset } from 'expo-asset';

// Keep splash screen visible during preparation
SplashScreen.preventAutoHideAsync();

function App() {
  const [isReady, setIsReady] = useState(false);

  useEffect(() => {
    async function prepare() {
      try {
        // Load critical assets in parallel
        await Promise.all([
          Font.loadAsync({
            'Inter-Regular': require('./assets/fonts/Inter-Regular.ttf'),
            'Inter-Bold': require('./assets/fonts/Inter-Bold.ttf'),
          }),
          Asset.loadAsync([
            require('./assets/images/logo.png'),
          ]),
        ]);
      } catch (e) {
        console.warn('Asset loading error:', e);
      } finally {
        setIsReady(true);
      }
    }
    prepare();
  }, []);

  useEffect(() => {
    if (isReady) {
      SplashScreen.hideAsync();
    }
  }, [isReady]);

  if (!isReady) return null;

  return <AppNavigator />;
}

Performance Checklist: Putting It All Together

Here's a prioritized checklist for optimizing your React Native app in 2026. Start from the top — each step builds on the previous one:

  1. Enable the New Architecture — The single biggest performance improvement. Verify all dependencies are compatible.
  2. Confirm Hermes is active — Make sure you're running Hermes (and consider opting into Hermes V1 on RN 0.82+).
  3. Enable React Compiler — Automatic memoization with zero code changes. Available by default in Expo SDK 54+.
  4. Audit your bundle — Visualize your bundle, remove unused dependencies, replace heavy libraries with lighter alternatives.
  5. Optimize lists — Migrate from FlatList to FlashList or Legend List for any list over 50 items.
  6. Optimize images — Use expo-image with WebP format, appropriate sizing, blurhash placeholders, and prefetching.
  7. Move animations to UI thread — Use Reanimated for all animations. Eliminate any Animated API usage that runs on the JS thread.
  8. Isolate frequently-updating state — Extract timers, counters, and real-time data into leaf components.
  9. Clean up subscriptions — Audit every useEffect for proper cleanup. Use the memory monitor hook to catch leaks early.
  10. Set up performance testing — Add Reassure tests for critical components. Catch regressions in CI before they reach users.

Wrapping Up

React Native performance optimization in 2026 is both easier and more impactful than it's ever been. The New Architecture, Hermes V1, and the React Compiler handle a lot of the heavy lifting automatically — but they're not magic. The best-performing apps combine these platform-level improvements with thoughtful component architecture, efficient data handling, and regular measurement.

Start with the architecture-level wins (New Architecture, Hermes, React Compiler), then work your way down to component-level optimizations like lists, images, and animations. Measure before and after every change with the profiling tools we covered. And most importantly, set up automated performance testing so that once you achieve great performance, it stays great as your app evolves.

The tools and techniques in this guide represent the current state of the art for React Native performance. As Hermes V1 matures with JIT compilation and Static Hermes features, and as the React Compiler continues to improve, the performance ceiling will only keep rising. Build your optimization practices now, and you'll be well-positioned to take advantage of every improvement that comes next.

About the Author Editorial Team

Our team of expert writers and editors.