Microservices have been around for a long time. Despite being a more complex solution than the more traditional Monolith, microservices are a good solution to many extremely common problems in software development, such as difficult and slow deploy process, large codebase that often lacks cohesiveness, difficulty for different teams to work simultaneously in somewhat related features due to conflicts, etc.

Nevertheless, Microservices are used almost exclusively on the Backend side. And this makes a lot of sense, since it is easier to provide a seamless experience, with a consolidation layer, such as the API the server side provides. It kind of hides everything that happens underneath. On the frontend, since it is always in front of the end user’s eyes, this is not so easy to achieve. Especially given how SPAs work and what is expected from them, and also given the popularity of global shared state patterns and libraries, such as Redux.

Now, this is exactly what Micro Frontends are needed for. To replicate the benefits that microservices bring to the server side, but this time directly in front of the final user in his browser. The main benefits we want to archive using Micro Frontends, are:

 

  • More cohesive codebase 
    Since we break the frontend into smaller pieces, we do that separating the code into smaller pieces of functionality (in many cases by page or part of the routing), resulting in a smaller, cohesive codebase, which is easier to understand and develop.
  • Simplify maintenance 
    Since each micro frontend takes full responsibility of a  particular part of the application, with little to none dependencies with the rest, it shortens both the development and testing time.
  • Allows to scale development teams
    More teams can work simultaneously on the project, as the collisions between them drop significantly, and teams can be more autonomous and decoupled from the rest.
  • Simplify updates 
    In a monolith, when in need to upgrade a certain dependency, or change a common component,it cannot be done unless the rest of the app will also support the change. Because of that, to introduce a breaking change is a costly task, that leads to a lot of development, and even more testing time to ensure everything works as expected. But if using Micro Frontends, we can introduce any changes we need in our module, without colliding with the rest of the application.
  • Independent deploy 
    Each micro frontend should have it’s own pipeline that would build, test and deploy the module all the way to production. Hence we can achieve continuous delivery where our updates arrive to production in question of minutes. 

Applications created using Micro Frontends look like regular applications to the end user, while each of the Micro Frontends have their own codebase, repository and pipeline.

To make it all work, we rely on the Container application, that will detect and understand which view is expected to be shown to the user, how it must be composed and built. And this can be achieve using one of the following approaches:

Integrate Micro Frontends in Build-time.

In this approach, all micro applications are put together in the Container application during the build process, and the result of this operation is then deployed to production. In this case, the Container application acts as a Monolith, and the micro applications are just dependencies that are used within.
The main problem that arises with this method, is that whenever a new version of a particular micro frontend is pushed, it is necessary to go to the main project (Container), bump the version of the micro service, and push that change. That meaning, the possibility of independent deployment of each micro frontend is lost. 

Integrate Micro Frontends in Run-time.

In this case, the Container Application will not auto-contain all the micro frontends, and they won’t be downloaded by the user’s browser whenever the application’s entry point is requested, but only when the specific route or view is reached in the main application. 


This can be achieved in two different ways:

– Using server-side includes.
We can use Nginx as a web server to differentiate what path (location) user is accessing, and return one HTML or another, as a response. For reference:

HTML template that can be used to achieve to ‘include’ the right micro frontend:

Welcome to the Container Application

 

 

 

 

Nginx Sample Configuration:

server {
    listen 8080;
    server_name apiumhub.com;
root /usr/share/nginx/html;
    index index.html;
    ssi on;
location /route1 {
      set $SELECTED_ROUTE 'route1';
    }
    location /route2 {
      set $SELECTED_ROUTE 'route2';
    }
}		

Nevertheless, this solution has its limitations, especially in terms of actual use of a simple page application, where we want to rely on shared state, or when the micro frontend to be presented is not defined by the route but by more complex conditions.

– Dynamically load the Micro frontends in the browser

In this situation, the Container application will load the needed micro frontend whenever is needed, by directly appending a <script> tag to the DOM with the src pointing to the requested application.

In here, comes a great example of how to achieve such behaviour using a custom hook in a regular React application, that allows to load the right bundle, and uses a custom event for communication across the services. But any other communication via Publisher / Subscriber suits perfectly, like pubsub-js library.