DEV Community

Nitro
Nitro

Posted on

How to build a React Native app 5x faster with Nitro

Nitro is a React Native build tool that optimizes build time up to 80% and can be easily integrated in any major CI platform.

This is the story of how it all started.

NOTE: for any technical question about Nitro please take a look at our FAQs

Motivation

Back in 2019, a new fintech company came to us looking for a technical partner with React Native experience to set up the foundations and create a team of RN developers. Since they had chosen to be mobile-first — meaning their users would be able to do everything from their mobile app — they were committed to create the best User Experience.

To honor that it was clear that iterating as fast as possible was a key to success. Creating a great UX demands constant feedback from your users, so keeping that feedback loop short is essential.

The plan was to have many squads working on different business verticals, each creating and improving their own features in their own environment without affecting other squads.

This led to many challenges and with the time some problems started to arise.

One of them became increasingly painful: the build time in our CI/CD.

The problem

During the first months our builds took no more than 10 minutes and that was something we could live with. However as the application grew (in complexity) the builds were slower each time until we couldn’t tolerate it anymore.

Every time a team wanted to test a new feature (or even a small bugfix!) a new build was triggered on our CI service (Bitrise in this case) so that any team member (tester, designer, product owner) could try the new version.

With builds taking around 30 minutes, team’s productivity started to fall, specially at the end of the sprint where all the teams were doing the well-known final touches & bug-fixing.

It always ended up with teams competing for a spot in the builds queue.

An awkward question was trending

Why are builds so slow? Can’t we do anything to make them faster?

Our answer at that time was always the same

This is not the web, mobile builds are slow.

Not happy giving that same answer each time we decided to deep dive into this issue.

The existing solutions

The build process for a React Native app (leaving aside the dependencies installation) is basically composed of three things:

Compiling native code

Bundling JS and assets

Signing the resulting artifact

NOTE: this is a simplified overview, more detail in an upcoming post

After a quick analysis we could see that between 70% and 80% of the time was used to Compile native code and the rest of time was mainly used to Bundle JS and assets, while Signing the artifact time was not significant.

React Native build phases and the time each one takes<br>

React Native build phases and the time each one takes

The evidence suggested that if we wanted to make a significant decrease in build time we had to find a way to speed up the native code compilation phase.

We started then looking for existing solutions.

At the beginning we considered using Expo as it is a well known tool to improve the dev experience, but the project required a couple of native dependencies that were not included in Expo’s SDK (e.g. custom SDK for biometry) so we discarded that option.

We also took a look at CodePush, but again, we couldn’t use it for production because we had some constraints regarding finance institution regulations.

We considered then using it just for pre-production stages, but thought it wasn’t worth it to modify app behavior depending on the environment — keeping the app as similar as possible across environments was something we wouldn’t sacrifice that easy.

Last but not least, we considered upgrading our CI service plan but the build time improvement wasn’t worth it compared to the cost.

None of these alternatives was a real solution, at least not one could bring back team’s productivity to early days.

A new approach

We knew what we wanted: optimize our build process to be under the 10 minutes without affecting the app behavior.

But we didn’t know if it was even possible.

One lucky day we came up with this idea

What if we do in build-time what CodePush does in runtime?

That way we would avoid changing our app behavior while saving us from the native code compilation phase at the same time.

So the experiment began…

The experiment

As you may know, applications files (.ipa file for iOS, .apk for Android) are just a .zip which holds everything an app needs to run. For React Native among other files we have the JS bundle (the file containing all your JS code).

If we managed to replace that file with a new one then our dream was possible.

But after injecting the new JS bundle within that .zip file and compressing the file again… the app wasn’t working.

Of course that was expected! We had corrupted the app content and the apps are digitally signed (remember that signing phase?).

The good news was that it is “easy” to solve — we were not hackers trying to introduce malicious code but the real owners, so we could just resign the new app with our keys/certificates.

That took the app back to life again.

This successful proof of concept opened up the door to a new way to improve our build process, so we immediately started working on a novel solution for this challenge.

Our goal was to replace the fastlane gym command with a custom script able to reuse previously built artifacts, but keeping the project with minimal changes. This is, no runtime dependencies for our app, no extra code required neither and minimal configuration if needed.

The results

A few weeks later we had a functional version of our build workflow and the results were astonishing.

The build time dropped more than 60% for optimized builds!

As most changes were non-native code (adding JS code, images, etc) this optimization was applied to more than 85% of the builds, creating a huge positive impact on the team and boosting its productivity.

Nitro’s Origin

At that time all we had was a set of custom scripts and configuration highly coupled to the project itself, but after seeing the good results we decided to go all-in and make it a build tool we could use in all of the projects we were working on.

So we created a CLI from scratch and implemented the same features needed for this project until we could replace the project’s custom scripts by our new CLI.

With every new project new features were required so we grew organically and always keeping in mind this new CLI should be generic enough to work on any new project. We also added new optimizations over time, making builds even faster.

After putting so much time and effort in it, we thought this internal tool deserved to be a product any React Native project could benefit from. That’s how Nitro came to life.

Nitro’s Future

We keep working on making Nitro faster, reliable and simple to use, and making sure is compatible with every upcoming React Native version so you can build with confidence.

More importantly, we want to make you and your team focus on your product while getting faster builds with minimum effort on each Nitro new version.

Do you want to try Nitro?

Get early access from our website or leave us a comment!

We would love to read your comments on this topic:

Have you ever faced this problem?
Have you tried solving it by yourself? How?
Do you think Nitro is a good solution?

About us

We’ve been building React Native apps since 2015 at Underscope and currently working full-time on Nitro to make it the fastest way to build any React Native project.

Top comments (0)