Migrating a monolithic web application to micro-frontends

The beginning of the journey

Developing large and sustainable frontend applications is very complex. When there are sometimes several hundreds of thousands of lines of code and many teams working simultaneously it is really challenging.

Within the last few years, a new frontend design pattern, micro-frontends, has emerged which aims to break up a frontend application into many smaller pieces by developing a new architecture to increase the global performance of the application and the efficiency of teams.

Is my project right for micro-frontends?

When pages or components of an application have enough complexity to justify dedicated teams to work on it separately, consider splitting into micro-frontends.

Let’s illustrate with a typical E-Commerce application which contains account management, products pages, cart management, checkout and customer service. One could see each of them as one micro-frontend embedded into a shell container.

Micro-frontends split
Naive E-Commerce micro-frontends split

Thanks to the split:

  • Each micro-frontend can be deployed independently
  • A team can be dedicated to each micro-frontend, improving ownership and responsibility

Questions to ask yourself before migrating to a micro-frontend architecture

The migration to micro-frontends is not a smooth process. It is essential to start with the end in mind.

1. What are the main problems you are trying to solve with micro-frontends?

You may want to split your monolithic front-end application to improve overall performance or to control the ownership and responsibility of each module. You may be scaling up or want to get rid of your framework by migrating modules one by one.

2. What is your medium to long term strategy for the frameworks you use for development?

You may already be planning to change your framework, or you may want to use a more powerful - and trendy - one, or you may be thinking of having several.

3. How do you want to deploy?

You want to deliver quickly, one micro-frontend or several at a time. You need to ensure that the quality remains high, and you have clear end-to-end testing strategy for this. You also need to control the cost of introducing a new micro-frontend into your pipeline and you will need to completely redesign your packaging process.

4. How can you ensure the consistency of the application with multiple teams?

Your design team has created a complete UX and UI design system and your application needs to be consistent across the board, from one micro-frontend to the others. You may need your dedicated teams to rely on the same shared components, without breakage or regression, to keep everything safe.

5. How do the micro-frontends interact with each other?

You have multiple interactions between micro-frontends through navigation, shortcuts or even you have micro-frontends whose logic leaks into other micro-frontends. You need to pay particular attention to identifying each use case, as this will impact on your micro-frontend implementation strategy.

Our Web Components strategy

At Contentsquare, with half a million lines of code in our AngularJS/Angular hybrid application, over 40 frontend developers and big technical and product challenges to overcome, the migration to a micro-frontend architecture was a necessity.

We consequently conducted several workshops before defining our target and ideal vision. Our main concerns were performance, scalability and interactions between modules.

As we wanted to control the migration of one module after another and the performance at the same time, two pitfalls had to be addressed:

  1. The impact on the build process, locally and in the continuous integration tool.
  2. Dependency and version management.

Migration from one module to another should be simple. Once you have the mechanism for the first one, you can replicate it for the others; and every improvement made to one will benefit all. So using a new single repository for all our micro-frontends was our first choice. It helped to control the two pitfalls.

Secondly, the integration of a micro-frontend into the legacy shell container had to be as simple as possible. Our second key choice was to rely on Web Components. It allows you to create custom elements in the DOM, to isolate the CSS in the shadow DOM and, by development, load it lazily.

Here is an example of an Account micro-frontend exposed as Web Component:

<script>
class Account extends HTMLElement {
...
}
window.customElements.define('app-account', Account);
</script>
<!-- Then you can use it in HTML directly -->
<app-account></app-account>

Migrating a module to a micro-frontend

It is the most sensible part: finding the budget to migrate a module that will be the same, but different in a way, is not a piece of cake. We have successfully experimented two different strategies to ease the process:

  1. A micro-frontend can begin by exposing a few Web Components. If you need to add a new modal, panel, section to an existing module, consider developing it as a Web Component instead, this way you can migrate parts of your module in baby steps.
  2. If the product team already has some ideas of making global changes to a module, it could be a good opportunity to migrate the module to a micro-frontend, as making big changes generally requires an important refactoring anyway.
Web Components migration strategy
Migration relying on Web Components

Adding a new module as a micro-frontend

It should be much easier. It eventually allows you to clarify your internal framework strategy and you can take this opportunity to choose a more relevant framework as long as you are not creating a Frankenstein application which will soon load all frameworks on earth on each URL it links.

Web Components migration strategy
Adding a new module

Migrating the shell container

Finally, once everything is a micro-frontend, the shell container should be migrated to the mono-repository. It is where the bootstrap happens, probably very coupled with the base framework doing the routing. The same strategy of migrating as much as possible should also be applied.

The idea is to identify the main services from the container and extract them to framework agnostic libraries. What remains in the shell should only be the part about wiring with the framework without business logic.

Shell migration
Shell migration

What’s next?

Contentsquare is not the first company to rely on the Web Components strategy to implement micro-frontends. While the migration plan seems simple, implementing it is a long race, a bit like a marathon, because you want to keep releasing features while migrating. So perfect alignment between the product team and our R&D is an ongoing effort.

Alongside this plan, the Web Components strategy enhances our capacity to easily add cross-module features by plugging in business logic developed from one of our micro-frontends to play in any other micro-frontend. It consequently facilitates the interaction between modules without logic leakage. Each micro-frontend can expose its Web Components, ready to be used anywhere.

Consider the following: the CART micro-frontend may expose a Web Component AddToCart with business logic inside to check the balance of the user or to indicate on the fly the amount of the cart. This Web Component could be used in all product pages which will never have to fetch any data to provide a balance or total amount or how to display it, because it is and will always be the only responsibility of the AddToCart Web Component. This is a new powerful tool for the marketing team.

In our next article, we will look at the technical side of this pattern to see how it can be achieved in an elegant way while keeping sight of the performance of the application.