Micro Frontends in a Monorepo

May 5, 2022

Organizing work on a large-scale software development project where multiple teams get to work concurrently can be tricky to get right.

This article will explore common approaches to organizing , whilst concentrating on micro frontends in a monorepository setup.

Overview

To set up some context, let’s assume we are building a trading application, which we used in our previous write-up on micro frontends.

Example frontend project.

As seen in the diagram above, the application consists of several separate sections which are to be developed by separate teams. Each team may have multiple technology stack options, but that is of no importance for the subject of this article. The most important aspect is that multiple teams are building a complex application, each team handling a different part of the system.

Available Software Development Approaches

There are several ways to organize the project’s structure when working on large-scale web applications. This article lists some of the most popular ones and discusses differences between them.

Each approach has various sub-variants. However, this article focuses on the most basic scenarios for each approach, whilst briefly mentioning possible deviations where applicable.

Developing Separate Applications

The first approach we are going to investigate is having separate applications. In this method, each composite application would be hosted in a separate repository and deployed to a separate location. It is the most flexible setup when multiple teams are working to deliver a complex application.

Developing separate apps as an approach to software development.

However, it also comes with some significant drawbacks. One drawback worth mentioning is the repetitive work that goes into maintaining the repository and CI/CD pipeline. Additionally, each application will maintain its own list of dependencies, which is prone to version clashes and has a negative effect on application performance as shared dependencies are included multiple times, unless a strategy like webpack externals is used.

On top of that, code reuse and consistency enforcement come with extra cost. To exemplify – if we wanted to extract a common component to be used by multiple teams in charting and news applications, we would need to create a separate repository containing the library, publish it to some sort of NPM registry and then pull it in as a third-party dependency in the projects. This lengthens the process and makes code reuse less likely. The reason for that is that the entire process to reuse a small piece of code is so lengthy that it overshadows the benefits of doing that.

Monorepository Development

The second approach is the use of a monorepository. It is the best option if a limited number of teams are working concurrently on the application.

The monorepository approach, however, is more troublesome as more teams get to work concurrently. As the number of teams goes up, it requires more planning, adequate branching strategies and feature toggles usage. These precautions are required to keep the application in a releasable state, allowing for the deployment of individual features, while other features are still being worked on by the remaining teams. Potential issues can be avoided by putting more emphasis on release planning, but the overall development speed is bound to be reduced.

Monorepository approach to frontend projects.

There are many benefits to the monorepository approach, which one might consider the opposite of the separate applications approach. Code reuse is simple and straightforward, since the entire codebase lives in one repository. There is a one-off CI/CD and repo configuration. Also, the third-party libraries footprint is minimal with no requirement for complex solutions like webpack externals.

The biggest drawback is the fact that this setup does not scale well when more teams are involved. In other words, the more teams work on a project organized in a monorepository the more cumbersome the process becomes due to the added care required to collaborate on a single codebase and single release artifact.

There are a few variations of the monorepository setup. One would be having separate dependency management for each composite application, bringing it closer to a separate applications setup living in a single repository. On top of that, teams can enable dependency sharing via yarn workspaces, where some dependencies are hosted at the root level.

Micro Frontends in a Monorepository

In order to counter some of the limitations of the monorepository approach, evaluate micro frontends in a monorepository. The differences are limited – most of the strengths and weaknesses of the monorepository approach still apply.

Micro frontends in a monorepository

One key differentiator which justifies the added complexity of the micro frontends architecture is the fact that you can deploy parts of the application (micro frontends) separately. Unlike in the monorepository approach, where you are dealing with a single deployable, you can cherry-pick the composite applications you would like to release as you would with separate, standalone applications. This ability to deploy independently reduces the need for convoluted branching techniques or feature toggles to guard any unfinished work from being visible to the end clients.

The ability to release parts of the application separately does not come at zero cost as deployment details for each micro frontend must be set. It may also require some additional work from a deployment pipeline perspective. However, these costs are minor when weighed against the benefits.

All other considerations of the monorepository approach still apply, as our micro frontends would be hosted in a single repository.

Comparison of Software Development Approaches

Below is a table that provides an overview of the most important differences between the three approaches.

Developing Separate AppsMonorepoMicro Frontends in a Monorepo
Repo, CI/CD and deployment setup costHighLowMedium
Code reuseHighLowLow
Multiple teams working concurrentlyLowHighMedium
Supports partial releasesYesNoYes
Dependency upgrade costsHighLowLow
Supports staged upgradesMaybeNoNo
3rd party code duplication in bundlesHighLowLow

Repo, CI/CD and Deployment Setup Cost

Both micro frontends in a monorepo and monorepo approaches yield considerably smaller initial setup cost (repo, CI/CD and deployment setup) since we are working in a single code repository. Additionally, when comparing micro frontends to a monorepository, the former must have their deployment details revisited each time a new one is added.

With the separate application approach, the team must set up all configurations each time a new composite application is added. That immensely increases the cost of development.

Code Reuse

Code reuse is easy and requires almost zero overhead in a monorepository setup. It mostly involves moving files and/or code around.

The difference is substantial when compared to separate apps, in which case the team must extract the code to a new or existing library. This library must then get published to a public or private npm repo for the applications to be able to consume it. In this case the entire process involves an additional release of a new version of an existing library or an entirely new library, which is more costly than just shuffling code around in a monorepository. More importantly, if code sharing is tedious developers tend to postpone it, further increasing the cost of development.

Multiple Teams Working Concurrently and Partial Releases

Having separate applications grants the most control, thus minimizing the number of clashes, merge conflicts, and dependence on feature toggles. In this approach, if the entire application is divided according to domains, owned by separate teams, the only place where multiple teams can overlap and clash are shared libraries. Other than that, work can proceed independently.
Working in a monorepository is very different - all the work happens in a single repository and there is a higher dependence on feature toggles and proper branching techniques. Considering the application needs to be released as a whole, proper release planning is complicating but crucial.

The micro frontends in a monorepository approach shares most of the pros and cons of a monorepository method, but allows for independent deployments and releases of parts of the application. That alleviates some of the complications associated with the monorepository approach.

Dependency Upgrades and Staged Upgrades

With or without micro frontends, in a monorepository setup, teams work with a single codebase. Thus, blanket changes like global dependency upgrades are applied in one go to the entire application. It saves time since it eliminates a lot of repetitive work, but poses greater risks as it is not possible to roll out a partially upgraded application. Partial upgrades might be possible in the case of micro frontends but it depends on the individual setup considering all the tooling restrictions and aspects of libraries one tries to upgrade.

The polar opposite are separate applications, where the team has to maintain and upgrade each composite application individually. This adds quite some overhead to the entire process of upgrades and maintenance but allows for gradual rollout of changes if the library allows for that at runtime and if it doesn’t cause version clashes with other parts of the application.

Third-Party Code Duplication in Bundles

A monorepository setup makes it easy to achieve a single version of any given third-party dependency to be included in the final bundle. We can either have a single package.json file defining the dependencies or common dependencies moved over to the shell of the application in case of micro frontends.

Third-party code duplication is not straightforward in the case of separate applications. Each composite application would hold a copy of all its third-party dependencies, making the total application weight considerably higher. Teams can implement dependency sharing using approaches like webpack externs, but this adds additional complexity and sacrifices the flexibility provided by the separate application setup.

Development flexibility suffers in this case since the versions of dependencies would be bound across composite applications, making the upgrades tricky. Additionally, such relationships between composite applications wouldn’t be very obvious and teams might miss them.

Takeaway

As pointed out in this article, micro frontends in a monorepo have many strengths and some weaknesses. The most notable improvements over the vanilla monorepository approach is in the release area. This improvement comes at the cost of added setup complexity.

There are some valid use cases where the use of micro frontends in a monorepository would be preferential, especially when a limited number of teams are working on the project and the possibility to deploy and release parts of the application is crucial. A thing to keep in mind is that those benefits must overweigh the added complexity and risk associated with implementing this innovative approach.

High-level of inter-team parallelization requires a flexible release model and branching to counter the inherent risks of parallelization. In that case, making separate applications is the best option. This comes at a cost but is however outweighed by the confidence granted by the guard rails that such an approach offers.
On the contrary, when team parallelization is low, the equation is flipped. The guard rails of developing separate applications still come at a high cost but they’re offering very little value. This is an opportunity to streamline, and a monorepo approach or one of its variants may be a good fit.

In the end, to optimize the development process organizations need to pick the right tool for the job, at the appropriate time, and react to changing circumstances to get the most benefit from the invested time and effort.

About the author
Artur Banas
Talk to a Merchant Support Specialist