Optimizing your React Native JS Bundle Size

Tanner West
React Native Rocket
5 min readOct 13, 2023

As an engineer working in React Native, you’re well aware that most of your application’s business logic, and all of its third-party JavaScript libraries are bundled together into one JS file and shipped alongside your app’s native code to the app stores. You also know that the larger that bundle, then the larger the app that the end user has to download, and the slower the app will start up, since it has to load that JS into memory before it can become interactive. If you’re looking for ways to make that JavaScript bundle smaller, this article is for you!

Inspect your bundle’s contents with `react-native-bundle-visualizer`

The first step to making your bundle smaller is to see how big it is in the first place and what’s in it. react-native-bundle-visualizer is the go-to tool for this. Check out the project on GitHub to learn how to install and run it (I prefer to use the npx command) . The result will be a super cool graph like this one that shows all your app’s JS modules and how they contribute to the overall size of the bundle.

As you can see, the app I’ve used for this example doesn’t depend on a ton of third party libraries. The bundle is just over 2 MB and only includes a few of the most common RN libraries: React Navigation, Reanimated, a few expo libraries, etc. But notice the blocks I’ve outlined in red. This project also includes two very popular JavaScript utility libraries: date-fns and lodash. Most engineers wouldn’t think twice about bringing these libraries into their project, but notice how in our project, they contribute almost 300 kb to the bundle size. That’s over 10% of the bundle.

Before we go any further, you may already be wondering if Metro (which is responsible for producing the final JS bundle), performs some tree-shaking step like Webpack and Rollup do. The answer is no, it doesn’t, but we’ll talk about alternatives to Metro in a bit. For now, let’s explore how to reduce the footprint of the two libraries we highlighted above.

“Cherry-pick” library imports when possible

In the app you see visualized above, I only imported a single module from Lodash and date-fns each:

import {chunk} from 'lodash'
import {format} from 'date-fns';

Metro saw that I was using the each library and bundled the whole kit and caboodle for each. Fortunately, each of these libraries are written in a way that will let me “cherry-pick” the functionality I want to prevent Metro from bundling the whole library. Replace what we did above with this:

import chunk from 'lodash/chunk'
import format from 'date-fns/format';

And rerun react-native-bundle-visualizer to see the new bundle:

As you can see, this change reduced the footprint of date-fns from 198 kb to 30 kb (an 85% reduction), and lodash from 85 kb to 16 kb (an 81% reduction). Not bad for such a simple change!

Unfortunately, this approach only works for libraries that export modules individually, which is always likely going to be utility libraries like date-fns and lodash, and not React Native UI libraries, for example. There are a few more steps you can take to optimize your JS bundle, though.

Enable Hermes on both platforms

Hermes is a JavaScript engine developed by Meta specifically for running JS in React Native. Hermes has been enabled by default for both platforms in new React Native projects since 0.70, replacing the JavaScriptCore engine that was used previously.

Hermes brings many advantages such as faster startup times, improved memory consumption, and most importanly for us here, smaller bundle size. Hermes achieves these advantages by precompiling JavaScript into bytecode at build time, so it loads an runs faster at runtime. If your project doesn’t already ship with Hermes on both platforms, enabling it can be an easy win when it comes to optimizing your JS bundle.

Consider using an alternative bundler

The most heavy handed approach to optimizing your JS bundle is by using an alternative bundler instead of Metro. These tools each take a different approach to bundling an app’s JS assets, and some are built on popular bundling tools built for the web. I do not personally have experience with these tools, but here is a quick synopsis of the most popular ones to jump start your research if you’re interested. Note that just using an alternative bundler won’t guarantee a smaller bundle, but their advanced features can give you more control over what ultimately ends up getting bundled.

  • Callstack’s Re.Pack is the most popular tool in this category. It builds on top of Webpack to bring that tool’s rich ecosystem to React Native. It’s best for cases where you need advanced features like symlinks and code splitting
  • Microsoft’s rnx-kit includes @rnx-kit/metro-serializer that builds on top of Metro to include custom plugins, as well as metro-serializer-esbuild which uses the esbuild bundler to process the JS bundle
  • react-native-esbuild claims to be “a drop-in replacement for Metro” and builds upon esbuild for speed and to enable tree shaking

Conclusion

As we’ve seen, there are a number of things you can do to optimize your React Native JavaScript bundle, and there is no one-size-fits all solution. And we didn’t even mention reducing the overall app size with native platforms optimizations, which merits its own series of articles I hope to write soon.

So what did I miss? What other JS bundle optimization techniques have you used? Let me know in the comments here or on X @useRNPatterns!

--

--