Micro frontend architecture is an approach to building an application's frontend as multiple independent modules. This speeds up the building process and gives development teams greater flexibility, as they can work on different modules without getting in each other's way. It’s also a source of other advantages.
- Allows to deploy the micro frontends independently.
- Adds “lazy loading” of your code.
- Seamlessly integrates with various frameworks on a single page
- Adopts a new framework incrementally, without needing to rewrite your current application.
- Compatible with any existing build system you might be using.
Micro frontend architecture is a relatively new concept for frontend development. However, on the backend side, dividing an application into simpler, more manageable pieces is a well-established practice known as microservice architecture. There are differences though, for example, Service Discovery mechanisms are only used with microservices.
The benefits of micro frontend architecture align with modern web development practices. Breaking the frontend into more manageable components allows organizations to enjoy a more flexible approach to web development. The advantages spread to other aspects of the process, such as troubleshooting or management.
Independent deployment
In a monolithic architecture, the development team must replace the entire application whenever a new feature is introduced. With micro frontend architecture, they only need to replace the module that contains the new feature.
Isolated failures
A system divided into smaller modules is easier to monitor than a large monolith. It makes it easier to diagnose potential errors.
A single error can take a monolithic application completely out of commission for some time. In the same scenario, for an application built with micro frontend architecture, your team would only need to recover the affected module.
Hiding implementation details
An application's ability to hide its implementation details is known as encapsulation. It's the practice of restricting access to the inner workings of a module, exposing only what is necessary for the user to utilize its functionality.
Micro frontend architecture improves frontend data management and service interactions, thus enabling encapsulation. It ensures that changes in one part of the application do not necessitate changes across other unrelated parts. This simplifies development and reduces the risk of system-wide failures.
Flexible decision-making
Micro frontend architecture gives the management team the flexibility to make decisions for each module of the product separately.
Similarly, upgrades and improvements can be made individually, rather than all at once. This reduces downtime and the risk of issues affecting the entire application. As Martin Fowler points out, “Each micro frontend can be upgraded whenever it makes sense, rather than being forced to stop the world and upgrade everything at once.”
With the benefits of micro fronted architecture, there are also downsides such as the risk of incorrectly splitting applications, poor suitability for smaller projects, and an increase in the overall code size.
The risk of splitting the frontend incorrectly and how to avoid it
Adopting micro frontend architecture for an existing monolithic application can be beneficial to your project, assuming that the splitting was done properly.
The risk of splitting the application incorrectly is higher when there are gaps in understanding the business model or the client's organization. That is why engineering teams should avoid splitting their frontend applications at the early stages of development.
A good indicator that an application was incorrectly split is that individual micro frontends exchange data with each other at unpredictable moments and more often than expected. You want them to be able to work independently; however, if one microservice constantly requires data from another to function, it is possible that these microservices should never have been separated.
To avoid that, engineering teams sometimes do something called a modular monolith.
This approach involves keeping the code in a single code repository while attempting to carve out smaller applications as independent modules within it.
It’s like creating micro frontends but without the final stage, where they are established as smaller sub-applications.
Engineering teams behave as if they have separated the application completely and look out for any errors before an actual split. This way, they avoid the high costs of fixing the mistakes after the fact.
The increase in the size of the application
The application divided into multiple modules will be larger than if it was developed as one.
Splitting your application into micro frontends comes with a tradeoff: it requires each module to carry additional code, due to the nature of the micro frontend architecture.
- Each module may need its own libraries and frameworks, though these can sometimes be shared with other modules.
- Additional code is necessary for communication between modules.
- Some code and resources may be repeated in different modules.
- Some modules might require their own distinct resources.
As a result, the final application might increase in size compared to its monolithic counterpart.
Poor suitability for small projects
While micro frontends excel in scaling large applications and teams, they introduce an additional layer of complexity. This may lead to higher costs and more maintenance work than a monolithic frontend would require. Small-scale projects rarely have the resources or the need to handle such complexity.
Also, small projects with limited functionality may not benefit from the separate deployment units that micro frontend architecture offers. Your project could end up with duplicated dependencies and increased bundle sizes. If not managed carefully, this can lead to increased usage of resources.
For small projects, adopting a micro frontend architecture could be overkill. Typically, such projects are well-managed with a simpler, more centralized architecture, which a monolithic approach can handle more efficiently.
The risk of creating inconsistencies between modules
The autonomy of each module allows teams to choose their designs, processes, and tech stacks. While this flexibility is desirable, it can create inconsistencies between the teams if left unchecked.
For example, a developer working on a React-based module may be unable to jump in and support the team working on a different module as they will have incompatible tech stacks.
The risk of duplicating the code
In micro frontend architecture, coordinating shared dependencies across different modules is challenging. This can result in similar functionalities being replicated in various parts of the application.
When separate teams work on different modules, clear communication is essential. Without it, there's a risk that parts of the code may be duplicated in multiple places.
To minimize the potential drawbacks of splitting your application, it is important to create a working environment that effectively runs and maintains your application Here are seven of the best practices that we employ for our clients when working with micro frontend architecture.
1. Experiment with feature flags
Feature flags allow for code deployment without immediately making new features available to users. This minimizes risk, as the code can be tested without affecting the entire user base.
Engineering teams use feature flags to test new features without impacting all users. This approach allows teams working on separate micro frontends to incrementally roll out and assess changes, proving particularly beneficial for A/B testing and canary releases.
Feature flags enable you to deploy code without exposing new features to all users right away. This approach reduces risk by allowing code testing that doesn’t impact your entire user base.
2. Implement a common integration layer
In the context of micro frontend architecture, the common integration layer refers to a unified system or set of protocols and tools that manage the communication and coordination between different modules.
The purpose of the common integration layer is to create a shared foundation upon which each micro frontend can operate independently while still being part of a larger, integrated architecture.
It standardizes how modules interact with each other, simplifying the development process, and reducing the complexity of the overall architecture.
3. Define clear boundaries for each module
Having well-defined boundaries for each module prevents responsibilities and functionalities from overlapping.
By defining the boundaries of each module, you facilitate easier management and scalability of individual micro frontends, making them more autonomous. This allows for the independent development and deployment of each module, contributing to a more efficient and robust micro frontend architecture.
Clear boundaries ensure that every team has a specific area of accountability, which reduces the likelihood of code and functionality duplication.
4. Streamline builds and deployments with a CI/CD pipeline approach
Automating the build and deployment process with a CI/CD pipeline ensures that each module can be tested, built, and deployed efficiently and consistently. The pipeline approach minimizes human error, accelerates time-to-market for new features, and improves overall software quality.
5. Monitor and log errors
Implement consistent error monitoring and logging practices across all micro frontends to enable teams to identify and resolve issues quickly. Adopt common error logging standards to help them track and analyze problems, leading to a more stable and reliable system.
6. Ensure compatibility with contract testing
Contract testing ensures the compatibility of interfaces between different micro frontends. Teams define and test contracts to confirm that changes in one micro frontend do not disrupt interactions with others, creating a more robust and resilient application structure.
7. Introduce a shared design system
Introducing a design system common for every module ensures a consistent user interface across the entire application. Providing teams with predefined components and styles allows them to maintain a brand identity and deliver a seamless user experience.
There are multiple viable strategies for implementing micro frontend applications, such as routing and navigation approaches, single-spa, and module federation, as well as build-time versus run-time integration techniques, to name just a few. These methods are not mutually exclusive and can sometimes be combined to achieve the desired result. But in general, the implementation process has 6 steps.
- Define micro frontends:
- Identify features - divide the application into standalone functions or modules, and assign a team to each module.
- Choose an integration method, for example:
- Meta-framework - like single-spa, which allows for the integration of micro frontends written using different frameworks.
- Module federation.
- Iframe - strong separation, but difficult communication methods.
- Establish communication principles, such as:
- DOM as API.
- Event Bus.
- Custom APIs.
- Define the deployment pipeline:
- Each micro frontend should be deployed independently.
- Each micro frontend should have its own version control, allowing for independent deployments and potential rollbacks.
- Independent monitoring for each micro frontend.
- Ongoing maintenance of micro frontends - avoiding technical debt.
There is no “one size fits all” answer to the question of how to implement micro frontend architecture. The appropriate implementation strategy will depend on multiple factors. The question is whether it is right for your project, or whether you are better off with a monolithic architecture.
Should your project implement micro frontend architecture?
While it certainly has its drawbacks and requirements, under the right conditions, micro frontend architecture offers more benefits than drawbacks. Avoiding common pitfalls can be achieved by adhering to best practices. We are predicting that in the future, more medium and large-sized projects will be implementing this architecture.