Let’s explore the concepts of micro-frontends and monorepos, their pros and cons, and how they can work together.
Frontend development has become increasingly complex over the years, and choosing the right architectural approach and development strategy is essential to building a performant, scalable and maintainable application.
When it comes to structuring and developing modern complex frontend applications, micro-frontends and monorepos have gained significant attention over the years. However, they both have their merits and drawbacks and how they complement one another and understanding this is vital for making informed decisions.
In this article, we will explore the concepts of micro-frontends and monrepos. It’s important to note that this is a basic article as part of our React Basics series, and we will include links for further reading at the end of this article.
Before we look into micro-frontends, let’s understand the traditional monolithic architecture for building frontend applications. In a monolithic application, all code is bundled together and deployed as a single unit. Therefore, if you want to make any tiny changes to the application, you have to rebuild and redeploy the entire application. The process is too slow, sometimes costly and, if not done properly, can cause a lot of bugs and downtime for the users of the applications. The inefficiency of monolithic architecture brought about the introduction of micro-frontends.
Micro-frontends are a software architecture approach where independently deliverable frontend application features are composed to create a whole application. In a micro-frontend application, frontend applications are decomposed into smaller apps or modules, and each of these modules is a separate application that can be developed by different teams with different technologies, tested and deployed independently.
If you’re familiar with backend development, a micro-frontend is an extension of microservice to the frontend world because they have similar concepts and have emerged as an architecture that allows you to better orchestrate the user experience you are building, by dividing the frontend into smaller, more manageable parts.
To better understand what micro-frontends are, let’s take a look at this pictorial diagram below:
Image Source: Martin Fowler
In the diagram above, we have three different micro-frontends. All these micro-frontends are separate features built by autonomous teams in a source code repository. They all have distinct responsibilities with separate build pipelines and are deployed as separate applications. So the orange, green and purple boxes are separate deployables, and all these deployables are basically composed into one in production. Each micro-frontend is deployed to production independently.
For example, in a complex ecommerce application, you could have separate micro-frontends for features like a shopping cart, product catalog, checkout page, payment page and user profile page. These features can be developed, scaled, tested and deployed independently.
There can be different scaling strategies for each module, and we can scale one module to handle more traffic without having to scale the entire application. You can scale the product catalog to handle more traffic since every user visits the page when they visit the application compared to other pages like the profile page.
Another scenario is that if you made some changes in the user profile page, and, for some reason, the user profile page is not working properly, then the entire application will not be affected. The user will still be able to visit the product catalog and shopping cart page.
Team autonomy: Unlike the traditional monolithic frontend architecture, micro-frontends enable numerous teams to work independently on the different modules of the application. The developers working on the projects can be separated into different teams, where each team can own a section of the application from ideation to production. This gives each team more ownership, and they can decide the best technologies to use, coding styles and architecture for their task, thereby leading to timely delivery of the product.
Technology choices: Since you can develop and maintain each app as modules independently and keep them separated from the whole application, micro-frontends allow you to use different frontend technology and frameworks for different parts of the application that are best suited for the task. This means you can have your host page written in Vue and some other modules written in React, as long as they maintain micro-frontend standards. As a result, teams are not constrained by a specific technology stack but can choose the technology that best suits their needs.
Independent deployment: Micro-frontends enable you to deploy frontend modules independently since several modules have different builds. There should be a continuous delivery pipeline for each micro-frontend, which builds, tests and deploys it to production, no matter where you host the code. As a result, you can re-deploy only what has changed rather than the entire application. What other teams are working on should not affect another.
Rapid implementation & development: Micro-frontends significantly increase development speed by allowing each function to be implemented independently. The entire application does not need to be executed in order to make changes or add something to a function. As a result, engineers can build frontend applications effortlessly and faster than ever before, improving implementation time significantly.
Scalability: An essential component of micro-frontend architecture is modularity, the ability to break down applications into smaller, independent units. Each module or component can scale independently, and the entire application can scale out by adding more micro-frontends, thereby facilitating scalability. As your project’s features grow, you can easily expand your application and add new teams, resources or functionalities with micro-frontends. Each team can take ownership by working on different specific modules simultaneously, thereby ensuring the application can expand seamlessly to meet evolving product requirements.
Increased complexity: Micro-frontend architecture, if not implemented properly, can significantly increase the complexity of the application. Inter-component communication between different parts of the module and orchestrating the deployment of decoupled components or micro-parts may become complex and may require additional efforts and even an additional technology stack to maintain. Implementing a micro-fronted requires a lot of coordination between the teams working it.
Routing: With routing, you can determine where to send a request based on its URL. In other words, it instructs the web server which application segment to run based on the request pathname. It is a vital aspect of data architecture and user experience and one of the most challenging aspects of designing frontend applications.
In fact, orchestrating decoupled frontend experiences becomes even more challenging, especially when the routes of nested components may need to be exposed to the user. In a situation where you have multiple micro apps running on the same host, it is mandatory to configure routing to work correctly for each application.
Payload: When JavaScript bundles are built independently, common dependencies are duplicated, thereby increasing the number of bytes we have to send over the network to end users. For example, if a micro-frontend requires the installation of a specific program for it to function, then the end users will be required to download a corresponding copy. I.e., if every micro-frontend includes its own copy of React, then end users will have to download React n
times. This can directly affect page performance and user engagement for most users who live in a geographical location that runs on poor internet infrastructure, and users might not return to an application with poor page performance, so it is crucial to consider download sizes.
Large and complex application: Micro-frontends are suitable for building and maintaining medium- to large-scale frontend applications with multiple functions. Since micro-frontends enable you to break down applications into smaller and more manageable modules, your application will be easier to scale and maintain when it becomes complex over time. Also, frontend applications often encounter scalability challenges as they grow. One of the advantages of micro-frontends is the ability to scale specific modules independently, resulting in a highly responsive and performant application.
Multiple independent teams: Use micro-frontends when you’re building a very complex application that requires collaboration among multiple teams. The micro-frontend approach is ideal for teams to work autonomously, with different teams responsible for building different parts of the application.
Multiple technology stacks: Use micro-frontends when you are building an application that involves diverse autonomous teams that make use of different technology stacks. Micro-frontends offer the flexibility that enables each team working on an independent module to choose technologies that are best suited to their specific requirements. It is likely to be very helpful to incorporate new technologies easily or to gradually migrate away from a legacy system in this manner.
It is important to remember that even with the numerous advantages of micro-frontends, they may not be the ideal solution for all scenarios.
Small, simple frontend application: Do not use a micro-frontend when you’re developing a simple, small application. When you choose micro-frontend architecture, you may add unnecessary complexity to the development process. Traditional monolithic architecture may be a better solution in this case. It is easy to manage and scale as a monolith.
Small team & limited resources: Do not consider a micro-frontend if you have a small development team with limited resources. It may be unnecessary to use a micro-frontend because it requires a significant investment in time and resources to develop and maintain. It requires more time and effort than the traditional monolithic architecture.
A monorepo is a software development strategy where code for many projects is stored in the same repository. As the name suggests, it’s named monorepo because it’s one (mono) single code repository containing many projects.
However, individual projects have separate life cycles as opposed to the ones in a traditional monolithic architecture. Monorepos are a way to manage multiple projects in a single repository without using micro-frontends.
Monorepos are not a silver bullet. They aren’t going to solve all your problems. You need to identify the current issue with your code base and why it’s not scaling, and then you can decide if you still want to go with monorepo.
Using monorepo, you can create multiple micro-frontend applications stored in a single repository and have different life cycles for deploying these individual micro-frontends in your corresponding runtimes. Pairing micro-frontends with a monorepo can solve some of the complexity involved in implementing micro-frontends.
Monorepos are sometimes referred to as monolithic repositories. However, monolithic architecture, used for building self-contained applications, should not be confused with monorepos. Also, It is important to note that monorepos are the exact opposite of multirepos. Monorepos consists of a single repository with multiple projects (think of it as a single big folder containing the entire codebase), while multirepos consists of different projects, with each having its own repository.
Monorepos became very popular when Google exposed that they were using a single repository for both Gmail and YouTube. In fact, Google uses the monorepo paradigm to organize their code structure. Other companies like Facebook, Twitter, Uber, Netflix, etc. also use monorepos.
Easier collaboration: Monorepos make it easier to collaborate among teams because the entire codebase is in a single repo. It will be easier to track changes and share code since everything is in one place, and everybody can seamlessly collaborate.
Simplified dependency management: A monorepo simplifies dependency management because you can easily use a dependency in the same repository even though the dependency is built separately or resides separately from your application. It also prevents dependency conflicts since all modules are hosted and sourced from a single repository. Also, there is only one package.json. So, there is no need to install dependencies in each repo whenever you update your dependencies.
Refactoring: Refactoring is easier in a monorepo because if you want to refactor something across multiple projects across the whole system, having everything in one repo and everything in one file system is almost certainly easier. Instead of doing a pull request for each of your reports and trying to figure out which order to build your changes, you only need to make one atomic pull request that contains all commits related to the feature you are working against.
Slower Git operations: Git operations can be extremely slow. Imagine millions of lines of code and commits and infinite loops of the whole system in a single repository and several engineers collaborating simultaneously on the same repo. A monorepo is also more complicated for Git repository integration because issues within large applications are more complex to track and resolve quickly.
Pipeline issues: It is relatively straightforward to create pipelines if you have multiple repositories. Whenever a change occurs in one of the repositories, we trigger a pipeline, which then executes whatever needs to be done. It builds artifacts, pushes them to a registry, runs tests, deploys somewhere and so on. However, if you’re using a monorepo, the pipeline is a little bit harder to execute. When someone makes changes to a monorepo, the pipeline needs to figure out which operation to perform, what changed, what it should trigger as a result of that change and what it should build and shouldn’t build. This operation can quickly become complicated.
Pull request assignment: Another issue is with pull requests. Monorepos introduce a lot of noise, and it can be challenging to distribute tasks of reviewing and merging pull requests when there is no separation between repositories while everything is together. What do you do when a pull request is created? How do you figure out whether you should review a pull request and who should review the pull request when there are a lot of collaborators making pull requests in the same repository? It gets especially complicated to do delicate tasks.
Longer build & testing time: Due to the increasing codebase size, the build and testing processes can take considerably longer, and the fact that a lot of code is in one place means it will take more time for your continuous integration to approve every PR.
I hope you enjoyed reading through this article. We’ve delved into the concepts of micro-frontends and monorepos and discussed their merits and drawbacks. Micro-frontends are ways to develop scalable frontend applications based on smaller and more manageable pieces. Each of these modules is a separate application that can be developed by different teams with different technologies, tested and deployed independently.
The main goal of micro-frontends is isolation—they enable individual modules to be implemented independently to form a whole application. Even though the micro-frontend isolation concept is great, it also creates additional work when it comes to sharing and managing libraries. However, monorepos, which accommodate several micro-frontends, can simplify these tasks.
Several challenges arise with micro-frontends, such as reusability, dependency management and performance. One of the ways to solve these issues is to utilize monorepos, which act as a single repository containing many projects with benefits like centralized dependencies, easy-to-manage versions and faster development iterations.
David Adeneye is a software developer and a technical writer passionate about making the web accessible for everyone. When he is not writing code or creating technical content, he spends time researching about how to design and develop good software products.