In this article, we will look at how by adopting a new two-repo approach to React Native development we are able to improve team efficiency and also opens up doors to many other options that were previously left out of reach due to the unique triple-domains nature of React Native.
The new approach itself is relatively simple to implement as the majority of work is in moving the native
A while back Wix Engineering shared a glimpse of their React Native app architecture for increasing the development efficiency in their large teams of 50+ React Native developers. While their original article looks at development problems from a very large project point-of-view(the 1% of RN projects), after digging deeper into the internals it soon occurred to me that there are parts that can be utilized to benefit even the smaller development teams(that’s us - aka the 99%).
To validate my assumptions I’ve decided to test this new idea on the opposite end of what Wix did, by fully implementing it in a one-developer React Native project, and the end result is surprisingly positive.
Because the core changes of the two-repos approach are very close to the base levels in our software design decisions, it impacts many of the leaf decisions that came after it. To explain the new approach without information overloading everyone, I’ve decided to split it into two parts.
Here in Part One, we will look at the impact from a mostly high-level point-of-view so we can examine the various second and third order of consequences of applying this to React Native development.
You can think of Part One as the “What and Why”, whereas in the future Part Two we will discuss the “How to do X,Y,Z” where we will deep dive into all technical bits from feature developments to going live on the app store using the two-repos setup.
And for those who want an earlier hands-on experience before Part Two, at the end of this article, you will find a small demo repo for you to try out.
Our new approach is a challenge to this current norm by splitting up the React and Native parts, and we will look at how this single decision is able to impact many other aspects of React Native software development.
npm i lodash
To achieve the main goal of determining the viability and practicality of this two-repos approach in real-life React Native development, I have set up the following test plan to validate the idea and used one of my live React Native applications BusDue as the testing ground.
Assumption :It’s viable and practical for greenfield app development
To simulate greenfield app development, I decided to completely rewrite BusDue and make some large changes along the way so a lot of things are newly written from scratch. The backend also went through a similar rewrite at the same time so we are closer to the rapidly changing environment of an early-stage greenfield project.
For example, the entire API was migrated from node/express to GraphQL+AWS Lambda. FrontEnd code went from JS to full TypeScript. State management also went through a redesign with more states delegated to hooks or the GraphQL client.
Some of the app functionality changes were made on the spot(I am the product/designer/developer :P) and sometimes reverted soon after because the end results weren’t what I wanted, this allowed me to test things in the early-stage setting where everything needs to be very flexible and react quickly to the constant requirement changes.
Assumption:It’s viable and practical for brownfield app development
Although the BusDue app’s business logic is largely a rewrite, there are still some parts and know-how that need to stay the same for backward compatibility reasons, for these, I need a port over and keep their existing behaviors so I don’t break current users of the app when they upgrade to the new version. For example, the reading and writing of data stored on a user’s device must be backward compatible.
Assumption:It’s viable and practical for small and mid size team
I am the only developer on BusDue, and since Wix already proved this works with 50+ developers, if I can prove this works with one developer, we have a very good chance that everything in the middle will also work.
After going through the whole process of rewriting and releasing BusDue v5 using the new two-repos setup, my conclusion is that this new development approach offers many benefits for both greenfield and brownfield projects.
And best of all rather than being a bunch of mutually exclusive decisions colliding against exciting practices, these benefits can be incrementally and optionally adopted, or further customized to your project needs.
Despite the name React Native seemingly calls for developers with skill in all three domains Android, iOS, JS, and a whole host of related knowledge such as app stores management and mobile CI/CD, when we actually look at the overall workload over a longer period of time we can see that they are not exactly linear.
For example, the native workload dominates at the beginning of a project and then slowly settles down over time, and there will be the occasional large spikes that require immediate attention for example to fix a blocking native bug or large RN upgrades forced by one of your dependencies.
For the majority of smaller projects out there having 2 or 3 RN developers with good native skillsets should be sufficient for most native work as the workload on the native side doesn’t really scale in relation to the feature/business development side(see above chart), it’s not uncommon to go through periods of little to no native changes.
You can certainly get away with just one native-focused developer at the start, but over the longer term, you increase the risk of developing bus-factor problems if you don’t duplicate this part of your team.
Support large feature development teams with just a few developers
And for teams with access to existing React web developers and looking to onboard them into the mobile app project, this setup also offers a more granular approach compared to the learning curve one must take on in the single-repo setup, this results in a much faster path to productivity regardless of which area the new dev decides to focus on first.
We can even commit these test codes if needed and they will only show up when we run the app from the native repo. For example here is a comparison of a typical “add a native library dependency” task.
Much faster interaction cycle and no need to deal with JS codebase errors.
Instantly load JS into well-tested binaries, no need to deal with any build problems on your machine
The time and mental energy saving here is similar to the native example above but just the opposite, we eliminated the native binary build times between fresh app start of the application as well as gaining the assurance that the native binary you are working is identical to everyone else’s.
When considering whether to adopt a big decision such as this, not only do we need to evaluate if the benefit applies to our own individual situations, but we also need a good understanding of the various potential risks that we might encounter.
I think there is a very low chance for RN to remove support for custom file paths because the concept itself is nothing new, it is pretty much an essential functionality that enabled setups such as monorepo.
And AFAIK there are currently many React projects out there that are inside some sort of monorepo structure and each of them probably has its own folder hierarchy design.
As for other RN libraries, my BusDue app uses many popular native libraries such as react-native-maps, react-native-navigation, react-native-bugsnag etc.
I have yet to encounter any problems with them even though the node module they reside in is three levels up.
So based on the experience so far I think we can safely assume support will continue for the foreseeable future.
It’s a win here for the new setup.
While I can’t speak for the future but at the time of writing this article I’ve already gone through two react-native upgrades under this two-repo setup. The upgrade process is no different than your standard setup, in fact, I would say it’s easier to upgrade react-native in a two-repo setup because we have faster native debugging cycles due to the fact that we don’t need to load up a huge JS codebase each time.
Yes. As you can see in this example commit the whole change basically consist of two main parts, “move native folders down 3 levels” and “adding some QoL scripts and tooling to aid development”.
For the latter, it’s less mysterious as it sounds, all the scripts and tooling are just helper functions that ultimately produce a line of a standard Xcode or Gradle command-line script which we can run in a standard terminal.
For example, our
yarn build-ios script simply constructs a
xcodebuild <args...> command for building the and ios archive, and the
yarn ios script constructs a
xcrun simctrl command for launching the app in a simulator. All these are exactly the same commands React Native itself prints out on the console during a normal build or run process.
So if you ever want to revert this all you need to do is move the folders back to where they were and remove the
../../.. from various path settings then you will get back a standard React-Native project.
The deployment process is mostly technical steps so my plan is to defer that part to Part Two. But just to give you an idea of what it’s like, here are the general iOS steps for shipping an app store binary for release.
(The process for Android is pretty much identical using Android equivalent commands)
Native-repo example build and publish your own native binaries
Note that the above demo projects are a slim-down ios-only version. It’s not the final setup that I use in my BusDue app, for example in my BusDue app each time I build the native codebase I am able to output any number of these binaries for different purposes.
After spending time developing and shipping a whole app rewrite under this architecture and then comparing the old process I’ve been using in the past, I really like the simplicity of this idea and all the various developer-empowering benefits it brings, I will definitely continue to explore and refine this setup in my current and future projects.
Of all the listed benefits, my number one favorite has to be that I no longer need to think about half of the stack during debugging, and since 90% of development time is spent on various forms of debugging, this really freed up a lot of my time and mental energy for other important things.
I honestly believe this two-repos development process is a very strong alternative to any React Native projects starting today, or brownfield projects that have hit the scaling wall due to the various pain points we discussed.
I hope you find this article useful and will consider giving the two-repos set up a try in your next project, thanks for reading, and happy coding!
Also published here.