Micro-frontends (MFEs) are a hot topic in web development circles these days. But what are they? In a nutshell, MFEs are a way of structuring a web application as a collection of small, independently deployable modules. Each module has its own UI, business logic, and data model, and can be developed, tested, and deployed independently of the other modules. Although micro-frontend as a term has been used in multiple articles and projects in the past, it wasn’t until Zack Jackson released the master plugin called Module federation in 2020 that the campaign for the concept really kicked off bringing widespread adoption and attention.
There are several benefits to using the micro-frontend architecture in new and existing projects with the most obvious of them is having greater flexibility and modularity in the design of your user interface and presentation layer. Each team can develop its own UI components without having to worry about breaking the features of other teams. Some other benefits include:
There are some drawbacks to using the micro-frontend architecture with the most notable being the possibility to produce a lot of code duplication if not done carefully and sometimes inevitable challenges with managing and sharing data between the components. Some other notable drawbacks are itemized below.
Finally, following the micro-frontend architecture can be difficult when it comes to deployment and keeping track of the current state of the overall application. If each team is responsible for deploying their own code, it can be hard to keep track of what happened where and when, creating confusion about what is the actual state of the application on a higher level.
All the downsides can be properly managed by using different tools and processes that are better suited for the job, however, some of which are covered in this article and others through further research.
There are several different ways to implement micro-frontend modules or components. The most common approach is to use a JavaScript library such as React, Vue or even SSR (server-side rendering) libraries and tools. Each team can develop its own UI components using the library of their choice and then use a tool such as webpack or rollup to build a single bundle that includes all the UI components.
Another approach is to use a framework such as Ember or Angular which helps to properly organize the components and make them easily detachable. It is important to note that with micro frontend architecture, each team can develop its own components using the framework or library of their choice and then use a tool such as webpack or rollup to build a single bundle that includes all the components. Examples of implementing micro-frontend architecture are widely available and one of the popular sources is the module federation examples repository on GitHub see https://github.com/module-federation/module-federation-examples for more information.
Monorepo is one of the widely used setups when working with git-based code repository. This concept allows access to other components locally and allows developers to easily make changes to components without additional efforts. Some teams, however, do not share code repositories and still work efficiently. One must consider how often the teams interact, how related the components are and decide on how to organize the code based on this information. It is also important to properly divide the components in such a way that they are as independent as possible code wise, data wise and feature wise.
Shared components or features could be organized in separate repositories or in a single repository separated by folders, it is recommended however that they are versioned. If a team changes a specific shared component, it should be reflected in the version of the component that is built after the change. A proper change log describing the change to the shared component should be kept ensuring effective documentation of changes made.
Individual components into single app or container
Aside from using build tools like webpack with the help of module federation, other tools such as React Router, Angular Router, Next.js, Astro or even Nginx can be used to create a single-page application which we will refer to as the container app. This set up usually includes all the components and acts as an entry point to the project. Each team can develop its own UI components and then use the choice tool to route between and present the different components as a single deliverable project or multiple small projects. It is important to note that making a container app requires additional effort such that some organizations assign the container app project to a dedicated team.
There are many options available when it comes to building the individual components, in most cases, the default build tool for the library or framework is usually enough. In other cases, third party tools like webpack can be integrated.
All components should have their dedicated build process or stage either as part of a bigger pipeline or individual pipelines. This allows the components to be truly independent and deployable separately. Teams can leverage containerization e.g., Docker to help organize the deployment process and builds. Each change to a component could be versioned using semantic versioning and deployment of version updates can be automated, controlled or manual depending on the business requirements.
For the most part, existing pipeline solutions such as GitLab and GitHub Actions, Jenkins and the likes should be sufficient for building and deploying individual components. It shouldn’t matter if they are deployed to the cloud or custom servers, pods, azure app service or even as files on s3 bucket, existing solutions should be sufficient as they are highly customizable to accommodate splitting a big project into smaller parts.
Individual components could also be deployed to private package registries such as nexus using NPM or other options available. This way the components could be easily imported, built and managed. Teams can decide which versions of third-party components they want to install and track the history of changes. This is especially useful for shared components and services.
It is important to note that while using webpack or rollup to build a single bundle that includes all the UI components from all the different teams is a good thing since it allows you to use any JavaScript library or framework, it does require you to have a build system in place that can handle the different dependencies effectively.
The container app mostly serves as the entry point of micro-frontend applications. Users will visit the container app which in turn decides which micro frontend component to render based on user interaction. Sometimes, the container app could contain a menu, header component and necessary structure needed to present the application.
Aside from using existing solutions built into frameworks and libraries for data transfer, routing and building a container for your components, a container apps can also be a simple or complicated Ngnix server, Express app, Astro project amongst other options available as their core role is to route traffic and present different components running on dedicated ports, url within the local network, Kubernetes clusters or public network. Different parts of the app could have their own firewall configurations, access levels, rights and still work together effectively without problems since each one will be running on its own mini network, container and port, rules can be applied on top level, load balancers can be configured, and traffic can be managed seamlessly.
Sharing data between different modules and components in an MFE based application can be tricky. There are some important parts to note and some basic rules that apply. For the most part, a state manager can be employed to help with data management of local components as well as sharing data between external components. State management tools like Redux perform well with micro frontend components as each team can manage their local data within their components and in addition expose possibility to share the data. In Angular, tools like RxJS and services with observables can be used as opposed to using an external state management tool to manage and share component data seamlessly.
One of the important things to note is that data should be segregated as much as possible, making them independent and unique to individual component should be the goal when receiving data from third party API’s or from user input. Minimizing the amount of data shared between the components and ensuring that shared state is managed in such a way that only the component responsible for modifying it has written access while others only consume / read the data. Updating data when multiple components are involved in the update should be done in a queue like manner where the first update to arrive is applied follow by the next to create a smooth transition of state and generally efficient system.
It is also important to ensure that components can ignore unnecessary data, events and changes in data that might cause them to re-render when no direct changes are made inside the component. Implementing efficient change detection strategy and unsubscribing to data stream when they are no longer needed should strongly recommended.
There are some best practices that should be followed when developing a micro-frontend application.
In conclusion, micro-frontend architecture is revolutionizing frontend development, presentation and management. It presents some exciting possibilities that everyone wants to be a part of. It is crucial that proper research is carried out before converting existing projects or making new projects leveraging the MFE architecture.