Tackle the challenges of working on large-scale projects involving multiple teams with Micro Frontends architecture. We present the what, how, and why of MFE, so you can decide whether it’s a good fit for your project.
Micro Frontends is a buzzword that surfaced a couple of years ago. However, we believe that the tooling has become good enough to make it a viable and stable choice for production only recently, which is why we decided to tackle this topic.
We’ll start with the types of challenges MFEs solve, point out their main benefits, and explain the way in which apps get served. To illustrate this even better, we’ve included our presentation of a demo application.
However, no solution is a silver bullet, and implementing a Micro Frontends architecture also comes with its set of challenges, which we will present in a separate article.
We should note that our framework of choice is Angular with Nx tooling, but the core concepts apply to all MFEs, no matter which tools are used. This article is also accompanied by a presentation from one of our Infinum JS Talks events.
Micro Frontends and monorepos – a great asset for large organizations
Large organizations are usually split into smaller teams, where each team has its own development and release cadence and is the owner of a specific part of the final product. At some point, that work has to merge into a product that is presented to the end user as a single entity.
Keeping multiple teams in sync is hard enough from the organizational perspective, but it’s even harder to keep the codebase quality on the desired level while adhering to common coding standards.
When working with multiple teams, you also want to avoid re-implementing the same things over and over again. For example, almost every application needs some sort of authentication, and you do not want each small part of the system implementing its own login and registration flows. There may also be some domain entities and data layers that could be shared between all the apps.
All of this can become challenging quite quickly, so we need to introduce some tools to help us manage. For codebase management, we can introduce a monorepo. A monorepo is a single repository that contains all the code for all parts of the product, including all the apps and shared pieces of code. There are clearly defined boundaries between various parts of the codebase that everyone has to stick to, common coding standards are enforced, and tooling is provided for working within this workspace.
Monorepos are a popular choice among many big-tech companies, including Google, Meta, Uber, etc. It is not the only solution, but it is the solution we will explore in this article, and it works quite well with Micro Frontends.
The benefits of using Micro Frontends
To address all the challenges mentioned above, we will introduce a monorepo with a Micro Frontend architecture. These two solutions do not necessarily have to be applied together, but they are a really good fit. The monorepo helps us to keep everything in one place and have common rules, while Micro Frontends change our build and release process to allow teams to work independently. MFEs achieve this by building various parts of the final product into separate smaller applications that can be worked on and released separately.
Micro Frontend architecture gives us some interesting abilities. There are three main benefits of using it.
Independent development and deployments
Micro applications are served under a common host application. Each Micro app is deployed independently. This allows separate teams to deploy their changes without having to wait for all teams to be ready for an in-sync deployment of all things at once.
You can think of MFEs as lazy-loading turned up to 11. With regular lazy-loading, JS chunks that get loaded are all part of a single set of build artifacts for one application. With MFEs, chunks are loaded from a different domain, and those chunks are part of some other application’s build artifacts.
Sharing common parts
Abstracting shared code into libraries is nothing new, and it can be done without MFE architecture and without a monorepo. However, MFEs take this to a new level.
On top of sharing the codebase via shared libraries, MFEs also allow making instances of modules themselves singletons across all MFE apps at runtime, even though there are multiple different builds that include the same module.
This way, the state can be shared between MFE apps in a seamless way. This is very powerful for cases when all the apps, for example, use the same authentication and authorization solution or if they all share some global state.
The way this kind of sharing is achieved is a bit technical and beyond the scope of this article, but when using Nx and Webpack, it means you have to utilize Webpack’s Module Federation plugin. This plugin is a relatively low-level build configuration plugin for Webpack that is not that easy to use directly. Instead, we recommend using Nx, which offers an additional level of abstraction and tooling over Module Federation, in order to make working with MFEs within the monorepo a bit easier.
By the way, if you’d like to know more, our friend Klemen talked about Module Federation at one of our Infinum JS Talks events.
Once you introduce MFEs, it becomes easier to create new teams and/or bootstrap new applications that sit somewhere inside your MFE architecture. Most other teams do not need to know much about it, if at all. A new app can one day just appear as a new link in your navigation menu. It will not break existing apps or their processes, but it will be able to utilize everything that was already built. This way, you can easily scale both work organization and development, which is a crucial benefit for large organizations.
How frontend applications get built and served
Let’s dig a bit deeper and see exactly how deploying MFEs independently works.
First, we have to take a look at Single Page Apps (SPAs). With SPAs, most of the code is executed in the user’s browser, and the server plays a minimal role. Application code is written in JS and gets bundled in various files that are served statically by the server. The server doesn’t actually have any meaningful runtime; it just serves the files over HTTP.
SPAs can have a large amount of JS that has to be loaded and executed on the client, resulting in poor performance. To help with this, we often implement code-splitting and lazy-loading of different parts of the application. With lazy-loading, chunks of built JS code are served only when the user navigates to a corresponding page.
However, even if you have lazy-loading, everything is still part of one large application build process – a monolith. If you make just one small page in one part of the application, the whole application has to be rebuilt and re-deployed. As applications grow larger, this might not be enough to ensure a scalable solution that has sane build times.
Micro Frontend Apps allow us to take lazy-loading a step further and split the application not just into separately loaded chunks but into completely independent applications, each with its own development and release schedule. What was previously part of the same build artifact that was served by something like S3 now becomes multiple apps, each served from its own place.
Webpack’s Module Federation plugin ensures that code that is shared between different apps is made a singleton across all the apps. This way, you only have one instance of each shared module instead of each app having its own instance. You can control what is considered a shared module and what is not.
How Micro Frontends work – a demo app
This article would be much too long if we went into implementation details and screenshots of how everything works in practice. Instead, we invite you to take a look at this video presentation, where we show a demo application and how it works. Feel free to watch the whole presentation, but the demo part is from 9:12 to 25:42.
The demo app is a media consumption app where movies, TV shows, and music make up different sections of the app which can be separated out into MFEs, and the host application can take care of the user’s session. You can also check out the accompanying repository.
Nx & tooling
Creating your own tools to help manage a large monorepo containing MFE apps is certainly possible, but it is best to use some of the existing solutions and build on top of them. Here is a good resource to see an overview and comparison of many such tools.
Our tool of choice is Nx. Here are some of its many features we found useful when working with MFEs inside a monorepo:
- Abstraction above Webpack’s Module Federation plugin
- Code generation, including the ability to write custom generators that suit your needs for code organization
- Setting boundaries that restrict what can be imported from where
- Build optimization
- Running scripts on multiple projects at the same time
- Visualizing dependencies between different parts of the system
- Figuring out which parts of the system were affected and need deployment after a particular change in the codebase
If you want to learn more, there are many good resources available online.
Of course, there is much more; these are just some of the most prominent resources that we have come across.
Consider your options with Micro Frontends
Micro Frontends can be a valid choice if the project you are working on is sufficiently large, with many people in different teams working on various parts of the whole solution.
We’ve presented the what, how, and why of MFEs, but in order to have a full picture of what using this type of architecture entails, you also need to be aware of the complexities and challenges involved.