React Native has seen a great market adoption since its inception and has come a long way in terms of creating super fast and performant apps. However, keeping apps performant is a marathon and it gets harder as we scale our apps. To keep apps performant, constant monitoring and optimization is necessary.
There are many factors that affect the performance of an app. Some may impact app startup time while others may impact the app’s runtime performance. In this post, we will go through some of the tools and techniques that can help you improve and maintain the performance of your React Native apps.
How important is performance?
Performance in today’s world isn’t just the cherry on top of the cake. It is a necessity. Recent studies and statistics show that 70% of users will abandon an app in favor of a competitor, if it loads slowly. According to a study by Google, even the slightest delay in app loading can lead to a significant drop in user engagement. Or, on the flip side, if an app loads fast, it can lead to a significant increase in conversions and better user retention.
With the rise of mobile apps, the competition is getting tougher and tougher, studies show that nearly one in every two apps are uninstalled within the first month of installation. Poor performance can be a deal breaker for your app.
Tools & techniques to improve performance
Here’s some of the tools and techniques that can help you improve the performance of your React Native apps.
Enable Hermes
Hermes is a new JavaScript engine optimized for running React Native apps; its goal is to improve app performance. It converts JS code to bytecode that enables faster startup time. Also, it does better memory management through improved garbage collection to reduce memory usage and improve runtime performance.
Impact: Better Startup time, lesser memory usage, better runtime performance.
New Architecture
React Native went through a massive architectural overhaul in the past couple of years, resulting in a whole new architecture based on Fabric and TurboModules. I discussed this new architecture in another post. As a recap, this new architecture mitigates one of the most critical performance bottlenecks in React Native: “the bridge”, using TurboModules. Fabric, a new rendering mechanism, brings features like view flattening, render pipeline along with React Concurrent Features to make UIs smoother and more responsive than ever before.
Impact: Better UI rendering for a smoother experience
Optimizing JS Bundle using Re.pack
For large scale apps, JS bundle size can be a big concern. As the app grows, the bundle size grows as well. This can lead to slower app startup time and slower runtime performance. As most of the logic for RN apps is written in JS, optimizing the JS bundle can have a huge impact on the performance of the app.
Using Re.pack can help you create multiple chunks of JS bundle and load them on demand. This can have a direct impact on startup time, as less of the JS bundle is loaded initially. Also, this can improve runtime performance, as less of the JS bundle is loaded in memory.
Impact: Smaller app size, faster startup, better runtime performance
Graphics & Images
To be on top, aesthetics and visuals in apps are essential. On the performance side, it can be challenging to handle the impact that high resolution images or complex graphics can have on the app. To keep the app performant, it is important to optimize images and graphics. On the simpler side, all images in the app can be passed through an optimizer to reduce their size while keeping quality intact. While the better solution would be to break down images into more manageable chunks and render them accordingly. Here are small tricks to manage images and graphics in your app.
- Gradients: for apps rendering gradients, images can be avoided in favor of rendering gradients directly in the app using react-native-linear-gradient.
- SVGs: Vector graphics are more scalable and optimized compared to raster graphics. For images that can be vectorized, using SVGs can bear fruit. Render SVGs directly in the app using react-native-svg; there is also a great tool to embed gradients in SVG paths.
- Skia: For apps with high graphics requirements, Skia can be a good solution. Skia is a cross-platform 2D graphics library built by Shopify. It can be a drop-in replacement for libraries like react-native-svg and react-native-linear-gradient. It offers a wide range of performant graphics rendering solutions.
- React Native Fast Image: For apps with a large number of images, using Fast Image can be a good solution. It is a wrapper around SDWebImage (iOS) and Glide (Android). It offers aggressive caching and prefetching of images to improve performance. It also enables features like border radius, gif support, authorization headers, etc. to make image usage more flexible.
- Use optimized images in better formats; for example; using WebP images can reduce the size of images by 30% while keeping the quality intact. Also, using SVGs can reduce the size of images by 90% while keeping the quality intact.
Impact: Better performance for images and graphics, smaller app size, lesser network cost
Optimizing large lists
For apps with large lists, it can be challenging to keep the frame rate smooth, especially when the list is scrolled. Also, when the list is scrolled, the app is more likely to run out of memory. To keep the app performant, it is important to optimize memory usage and frame rate. Here are some of the techniques that can help you optimize large lists.
- Flashlist – A drop-in replacement of Flatlist, with 5x improvement on UI thread and 10x on JS thread. It does better memory management by avoiding recreation of items as users scroll through the list. It also offers features like sticky headers, pull to refresh, etc.
- RecyclerView – A JS-only list view, inspired by both RecyclerView on Android and UICollectionView on iOS. It offers a range of features to create aesthetically pleasing lists with smooth scrolling and memory optimization. It is battle tested at Flipkart and has been used in production.
- Optimized Flatlist – React Native’s default list component offers a range of options to ensure both memory and frame rate optimization. If you are using Flatlist, make sure to use
getItemLayout
prop to optimize memory usage. Also, useinitialNumToRender
andmaxToRenderPerBatch
props to optimize frame rate.
Impact: Better performance for large lists, lesser memory usage, better frame rate
Animations
Like graphics, animations are also a big part of great user experience. At the same time, Janky animations can be a big turn off for users. Performance of animations is one of the most critical vectors in app performance. Implementing animations in a performant way can involve some complex math and algorithms. But, even with that, it boils down to how well the implementation of animations is done on the platform. Here are tools to handle complex animations in a performant way.
- React Native Reanimated – It aims to provide ways of offloading animation and event handling logic off of the JS thread to the UI thread. This is achieved by defining Reanimated worklets β tiny chunks of JavaScript code that can be moved to a separate JavaScript VM and executed synchronously on the UI thread. This makes it possible to respond to touch events immediately and update the UI within the same frame when the event happens, without worrying about the load that is put on the main JavaScript thread. Thus animations built with Reanimated are smoother and more responsive even on low-end devices.
- Lottie – Lottie is a library for Android, iOS, Web, and Windows that parses Adobe After Effects animations exported as json with Bodymovin, and renders them natively on mobile and on the web. It also takes away the complexity of creating animations from scratch and provides a wide range of animations that can be used in the app. It also enables designers to plug their animations into the app without any coding.
Impact: Smoother animations, better user experience
Adaptability
Performance and adaptability go hand in hand. For a great user experience and to keep users engaged, it is important for apps to adapt to the device’s capabilities at any given time. For example, if the device is low on memory, the app should be able to adapt to it and not crash. Similarly, if the device is low on battery, the app should be able to adapt to it and not drain the battery.
- React Native Device Info – A library to get device information like memory, battery, etc. It can be used to get device information and adapt the app accordingly. For example, if the device is low on memory or battery, the app can adapt by not loading complex graphics and falling back to simpler ones. Or, in some cases, render lesser pages ahead in the list, or avoid styles that consume more memory like shadows, etc.
- React Native Netinfo – Adapting to network quality is also important for data heavy apps. Using this library, the app can get information about the network connection and its quality. Leveraging this information, it can defer unnecessary network calls to consume lesser bandwidth or avoid features like prefetching that consume more bandwidth.
Impact: Better user experience, lesser crashes, lesser battery drain
Gestures
Gestures are a big part of the user experience. It is important to make sure that gestures are smooth and responsive. Having lag in gestures makes the app feel unresponsive and can result in drop off. Thanks to React Native Gesture Handler, performant gestures are now easier to implement. It provides a declarative API for creating gestures and animations and coupled with Reanimated, it can be used to create complex gestures and animations.
Impact: Smoother gestures
Navigation
Navigation works as the backbone of the app and can have a big impact on performance. Thanks to great libraries like React Navigation, navigation is now easier than ever. Adding React Native Screens makes navigation even more performant. Also, opting for react-navigation-native-stack instead of react-navigation-stack can bring some big performance improvements. @react-navigation/native-stack
offers native performance as it leverages native APIs UINavigationController
on iOS and Fragment
on Android. It also offers more native UI features like large titles; though itβs not as customizable as react-navigation-stack.
Moreover, optimizing options in the tab navigator can improve memory usage and frame rate.
Impact: Better performance for navigation
Performance checks in CI/CD
Performance is a continuous process and requires constant monitoring and optimization. As we keep adding more features, it is important to make sure that performance is not compromised. It is important to have performance checks in CI/CD to ensure that performance benchmarks are not broken. Tools like React Native Reassure and Shopify’s React Native Performance are easy to integrate and can be used to run performance checks in CI/CD. These tools can give you a good idea about startup time, memory usage, frame rate, etc. and can be used to set performance benchmarks.
Parting thoughts
Performance is a marathon and not a sprint. It is a continuous process and requires constant monitoring and optimization. In this article, we discussed some of the tools and techniques that can help you improve the performance of your React Native apps. We will be adding more tools and techniques in the future. If you have any suggestions or feedback, please feel free to add comments below.