Why Knowing Your App’s Bundle Contents Matters for React Native Performance

Picture this: you’ve just launched your latest React Native app, and it’s a hit! People love the design, the features, the experience—but there’s one nagging issue. Some users start to complain that your app takes too long to load, especially on older devices or in regions of the world where fast wireless connections are not common.

You check the code, and everything seems fine at first glance. But deep down, you know the truth: something is wrong with your app’s loading process, which might be slowing things down.

App bundle size often impacts application size and performance. Although its influence was greatly reduced with the introduction of Hermes and its ahead-of-time compilation, it wasn’t eliminated completely.

Hermes and ahead-of-time compilation don’t ring any bells? Watch this video to learn more about them.

While parsing is no longer done at runtime, the code still needs to be executed, which may increase memory footprint and startup time. This is especially critical for iOS App Clips and Android Instant Apps, which have strict size limits and require careful optimization to meet platform constraints. It also applies to over-the-air updates, as the larger the bundle, the longer it will take to download—especially in bandwidth-constrained scenarios and regions of the world.

What can go wrong? Many things. You might include a huge library only to use a small portion of it. Other times, your dependencies may require different versions of the same library, and both end up in your bundle, wasting space. It’s not easy to catch all of these issues with the naked eye. And that’s where Expo Atlas comes in.

Enter Expo Atlas

I would compare Expo Atlas to an X-ray machine. It allows you to peek behind the curtain and inspect your React Native app’s bundle, module by module. Think of it as a map that shows you how all the pieces of your app fit together, from external dependencies to internal modules. It’s incredibly useful for understanding exactly what contributes the most to your bundle size.

Here’s how it works: once you enable Expo Atlas, it hooks into the Metro bundler to serialize the dependency tree and later uses it to display detailed insights about your app’s structure. It can break down the bundle, highlight large dependencies, and show you which parts of your app take up the most space. With this information, you can make informed decisions about what to optimize or remove.

Enabling Expo Atlas in an Expo project

Sounds like rocket science? Don’t worry! It’s dead simple to use. If you’re working with an Expo project, all you need to do is set the environment variable EXPO_UNSTABLE_ATLAS and run expo start like this: EXPO_UNSTABLE_ATLAS=true expo start. The last step is to press Shift + M to access additional tools and select the ‘Open expo-atlas’ option. A new web browser window will pop up, giving you access to an interactive diagram and allowing you to explore it as deeply as you want.

Key insights from Expo Atlas analysis

This analysis provides you with valuable insights into key metrics and relationships.

Assess the size of each dependency

You can now analyze your bundle and determine the percentage of total size taken up by each dependency. The same goes for your code—you can check if the new feature you’re about to release is too large.

Assess the size of each dependency in your bundle
Moment is not the best choice for a time manipulation library. It's rather heavy, and now I can clearly see that.

Understand the purpose of each module

When examining an individual module, you can easily identify why it’s included by seeing which other modules depend on it. Additionally, you can view which modules this particular one relies on.

Understand the purpose of each module in your bundle
Aha! Now I know exactly why @react-navigation/elements is present in my bundle.

Compare code before and after transpilation

You can also compare your source code before and after transpilation, which can be especially helpful when debugging tricky issues where something appears off in the output code, but nothing seems unusual in the source code.

Compare code before and after transpilation
Woah! I didn't write that! Metro silently transpiled my ESM module into CommonJS, and now I can inspect it.

This analysis can also be applied to all variants of your application bundle across different platforms.

How does Expo Atlas compare to similar tools?

You’ve probably heard of other tools that also give you insights about your bundle contents. What would make you switch, for example, from Source Map Explorer to Expo Atlas?

The key difference is that Expo Atlas is purpose-built for React Native and integrates seamlessly with the Expo ecosystem. Source Map Explorer doesn’t implement any React Native-specific heuristics and relies solely on source maps. They can be used for peeking into a React Native bundle, but unfortunately, they often lack information about the origins of certain parts of the bundle, such as code generated by Babel plugins. These sections show up as unmapped regions, which can account for as much as 30% of your bundle size. Expo Atlas, on the other hand, is able to resolve these unmapped regions because it uses the dependency graph of Metro—which gives you a far more complete and actionable view of your app’s structure.

That said, Expo Atlas focuses primarily on JavaScript-level insights and doesn’t directly analyze native dependencies or native modules in your app. It’s still a handy tool for anyone focused on building efficient React Native apps.

Using Expo Atlas with vanilla React Native

Now, here’s the catch: Expo Atlas is designed to work seamlessly with Expo projects. If you’re working on a vanilla React Native app, you’re probably thinking, “Great, but what about me?”

Normally, it’s tricky to get Expo Atlas running in a non-Expo environment. That’s because it relies on Expo’s Metro serializer to, well, serialize the dependency graph and expose it for further analysis. But don’t worry, there’s a solution. By recreating Metro’s built-in serializer and chaining it with the Expo Atlas serializer, you can achieve the same outcome in your vanilla React Native app.

Sounds complicated? It doesn’t have to be. If you don’t want to roll up your sleeves and write custom serializers, there’s an easier way: the expo-atlas-without-expo package. This package does it all for you, so the only thing left to do is install it and use the exposed serializer in place of the original one. That’s it! You’ll have access to all the features of Expo Atlas without needing to use the full Expo framework. There are no performance or feature penalties when using this approach.

Why Expo Atlas matters

It’s not easy to build a great application without a way to inspect it from the inside. There are many dangers along the way, just waiting for us to make a mistake and underestimate the power of duplicated or simply large dependencies.

Expo Atlas gives you the opportunity to precisely strike back, without guessing or following your gut. It’s especially important in regions where fast wireless connections are not yet common, as your app size may be a significant culprit for adoption. It also applies to you if you use over-the-air update solutions or work on iOS App Clips or Android Instant Apps.

Whether you’re working in the Expo ecosystem or using vanilla React Native, there’s no excuse for flying blind when it comes to your app’s bundle—and using tools like Expo Atlas for React Native ensures you’re not shipping bloated dependencies or slowing down app startup time.

Insights

Learn more about Performance

cover

Jul 3Podcast

Why Nitro Is More Than a Faster Bridge

Marc Rousavy (Margelo) talks Nitro modules, native state, zero-copy data, Vision Camera, and why speed is only part of the story.

cover

May 25Article

Profile React Components in React Native Release Builds

Learn how to profile React components in React Native release builds with @callstack/inspector, using an Expo app setup that requires zero native code changes.

cover

Nov 21Article

Memory Ownership Models: When JavaScript Meets Native Code

A deep dive into how React Native handles memory when JavaScript and native code share ArrayBuffer data. Learn why async processing can break, how GC affects raw pointers, and the safe patterns for building high-performance native integrations.

cover

Oct 14Article

From Files to Buffers: Building Real-Time Audio Pipelines in React Native

Learn how to move beyond file-based audio APIs and work directly with in-memory PCM data in React Native. This guide shows how to construct WAV buffers, use JSI for zero-copy transfers, and enable real-time audio processing.

cover

Sep 8Article

How to Add Custom JSI Bindings in Your TurboModule (and Why You Might Need Them)

TurboModules provide a type-safe way to write native code in React Native. JSI sits underneath as a lower-level API, enabling direct interaction with the JavaScript runtime. Beyond what TurboModules expose, JSI gives you access to powerful features, such as working directly with ArrayBuffers. This article shows how to inject a custom binding step by step.

cover

Jul 24Article

Exploring Fibers: Cooperative Multitasking and Lock-Free Task Execution

Explore how fibers enable lightweight, cooperative multitasking. This guide walks through building a simple scheduler, managing task dependencies without locks, and using Tracy to visualize performance.

cover

May 21Article

Multithreading Isn’t Free: Performance Pitfalls Visualized

Learn how to profile and optimize multithreaded apps. Discover why more threads don’t always mean faster results and how bad thread contention really is.

Event Cover

Apr 28Webinar

Master JavaScript Performance in React Native (Part 2)

Learn to fix JS performance issues in React Native-from debounce bugs to Redux selectors, in this hands-on webinar with real code examples.

Events Cover

Apr 24Talk

Tips & Tricks From The Ultimate Guide to React Native Optimization 2025

View flattening, React 19 hooks, real DevTools tips-learn what actually helps your React Native app run faster.

Events Cover

Apr 24Talk

User-Focused Performance Tracking

Discover how Expensify defines and tracks performance metrics that actually impact user experience.

cover

Dec 18Podcast

React Native FastIO: Nitro-Powered Performance Boost

Listen to Mike Grabowski talk about React Native FastIO, a collection of primitives for working with data in React Native and its performance benefits.

cover

Oct 15Podcast

Superfast Nitro Modules With Marc Rousavy

Even faster React Native apps? That's possible with Nitro Modules! Tune in to the conversation with their creator, Marc Rousavy.

cover

Mar 19Tutorial

The Hard Way vs. The React Native AI SDK Way: Stop Writing Custom Modules for Every Model

Learn how React Native AI uses a unified provider model to switch between cloud and on-device LLMs without rewriting application logic, enabling reliable AI experiences online and offline.

cover

Mar 12Tutorial

Build Smarter Apps: Tool Calling & AI Orchestration Explained

Learn how on-device models in React Native can call tools, interact with external APIs, and return structured outputs you can use directly in application logic.

cover

Mar 5Tutorial

MLC LLM + React Native: On-Device AI Without the Pain

Learn how to run third-party on-device LLMs in React Native using MLC LLM. Choose your models, run them efficiently across platforms, and keep a unified JavaScript API with React Native AI.

cover

Feb 24Tutorial

Intelligent Fallbacks with Apple Intelligence in React Native

Learn how to use Apple’s built-in Foundation Models in React Native apps, reduce memory usage, and enable fast, fully on-device AI with React Native AI Apple.

cover

Feb 10Tutorial

What Is the React Native AI SDK? A Complete Intro & Quickstart

Learn why on-device AI matters for React Native apps, how local LLMs behave offline, and what problems React Native AI is designed to solve from day one.

cover

Jan 29Tutorial

Implementing an Android TurboModule with Kotlin

Learn how to implement a React Native TurboModule on Android, from the TypeScript spec and Codegen to the Kotlin implementation and package registration.

cover

Jan 19Tutorial

Working with Different Threads in Swift TurboModules

Learn how JavaScript, native module, background, and main threads interact in React Native TurboModules, and how to use GCD queues to run async work safely in Swift.

cover

Jan 9Tutorial

How to Add Type-Safe Constants to Swift TurboModules

Learn how to expose typed, immutable constants from a Swift TurboModule using Codegen, and get full end-to-end type safety between native and JavaScript.

cover

Dec 30Tutorial

Adding Event Emitters to Your TurboModule in Swift

Learn how to emit native events from a Swift TurboModule and subscribe to them in JavaScript, including proper memory management and Codegen integration.

cover

Dec 22Tutorial

Writing Your First TurboModule in Swift

Learn how to build a fully working TurboModule in Swift, integrate it with Codegen, and bridge it through Objective‑C inside an Expo app.