Micro Frontends: the microservice idea for frontend explained

  • Lucien - Software Architect
    Lucien Immink
    Software Architect

Creating scalable, maintainable frontends is very hard. Where backend systems have been broken up into many smaller micro services years ago many frontends are still built as a monolith. In this article I will describe a recent trend where the frontend is broken up into many smaller, manageable pieces and how this can help to increase the effectiveness and efficiency of the frontend team(s).

Writing a progressive, responsive web application starts with setting up the toolchain and integrating the various steps needed to come from code to a working web application. What if you want to add a new feature and can’t find an easy place to start to integrate this with the current codebase? What if you want to progress with the language and want to start using some of the newer JavaScript features; or perhaps use a different pre-compiler? Or perhaps new colleagues are added to either the current or a completely new team that will also work on the current front-end?


Due to the inherit complexity of a monolith, combined with a specific (perhaps considered outdated) library or framework, the chances of people stepping on each other’s toes is a profound risk. These are all real problems that can negatively affect both the scalability and maintainability of the frontend and thus negatively affect the experience of the users.

In recent years more and more attention is being paid to the overall architecture and structures that are needed for modern web development. The pattern for decomposing frontends into smaller, easier to understand modules that allow for easy development, testing and deployment is widely used within the frontend itself by using component libraries, based on the atomic design approach. Can we apply these types of decomposition to the frontend itself? Yes, these are called micro frontends.

Why micro frontends?

Let’s look at some of the benefits of micro frontends.

Simpler codebase

A part is smaller than the whole. Smaller codebases tend to be simpler, easier to understand and handle. Since the codebase is isolated there is no need to overthink unwilling side-effects that could influence the rest of the application. No unwilling and unintentional coupling between components that should not know each other.

The libraries and frameworks used should be beneficial to the feature, not the platform. If it’s more beneficial to develop micro frontend A using React while micro frontend B is more suited to be built with angular or Vue then that should be possible.

Incremental updates

Instead of needing to integrate a new feature in the old, large frontend monolith, micro frontends give the ability to update/deploy just that micro frontend while the rest of the application remains untouched. Monoliths tend to grow, in size and grow outdated in time. The feeling that a complete rewrite of the codebase is the only viable solution is something most developers have dealt with and are still dealing with. That feeling is suppressed by deadlines and budget. With micro frontends that feeling can still arise. In some cases, a rewrite is the most appropriate solution. In that case it’s only the specific frontend that needs a rewrite, pressing less on budget and deadlines.

Instead of following the strict guidelines the monolith (e.g. framework or specific build pipelines) brings we can afford more freedom to make choices based on the features and requests of the micro front-end. Breaking changes can be implemented when it makes the most sense instead of a feature freeze to upgrade everything at once. Experimentation with new technology or new interaction models is encouraged since we work in a more isolated environment than ever before.

Autonomous CI/CD

Micro services shook the world due to the easy and controllable CI/CD flows. This is the same for micro frontends. Less code and less complexity results in faster and more stable code that builds faster and is easier to test. Regardless of where and how the micro frontends are hosted, each micro frontend should have its own CI/CD pipeline. The time-to-live is reduced enormously and should be encouraged to be as low as possible. Only when the API of the micro frontend changes we need to test the integration with the micro frontend with the other services in the application.

Micro frontends autonomous CI/CD

Source: Martin Fowler

Dedicated teams

If the micro frontend is only used and combined with:

  • a Backend for frontend (BFF)
  • a specific micro service that stores all persistent data in
  • a dedicated database

we observe a vertical slice.

Micro frontends vertical slice

In this set-up the front- and backend developers work closely together based on features. A search bar and it’s result page is something the “search” team implements while the “product” team works on the product overview and detail and the “shopping” team is responsible for the shopping cart and order process; which uses products delivered by the “product” team.

Another approach separates the front- and backend by an API (REST or GraphQL are commonly uses here) while still splitting the frontend in different teams.

Ownership and micro frontends

Source: Martin Fowler

Both approaches need to pay special attention to shared components and the first question is: do we want shared components? Shared components need to written framework agnostically since Team AB, and C might use different libraries and frameworks. Shared components also have their own release cycle and inherit complexity and overhead since they have components that might not be used in all but some micro frontends. The upside of shared components is that logic only needs to written once and can be implemented multiple times. Shared components will also enforce a more unified user experience.

The beauty of micro frontends is that you can do both: use shared components and develop components that might be promoted to a shared component when the time is right.

In short

Micro frontends are about slicing up monoliths into more manageable pieces and being more explicit about the dependencies. By being independent the development can be more feature driven and less focussed on release cycles.

How?

Since the idea is to be separate the coupling is loose. In most cases we end up with one container application, or orchestrator and the different micro frontends consist of their own page(s). The container application is responsible for the following:

  • Header, menu, footer, and other common page elements
  • Authentication and navigation between the micro frontends
  • Orchestrates the micro frontend where and when to render itself

There are a few approaches to have this orchestration:

Server-side composition

Either by using the webserver or the BFF to render the correct micro frontend server-side and then send the output to the client. This relies on micro frontends that render an HTML page as output that do not overlap the features of the orchestrator. Note: I’m not referring to server-side-rendering in this scenario; the rendering inside the micro frontend can still be client-side if that is required.

Example

Orchestrator
<body>
  <div id="app1">App1 will render it's content in this placeholder</div>
  <script>
    loadApp("app1").then((app) => {
      renderAppTo("#app1");
    });
  </script>
</body>

Build-time composition

The micro frontend could be published as a package. The orchestrator can depend and include the micro frontend. This does mean that the orchestrator needs to be build and deployed when a micro frontend changes. It’s better to integrate during run-time instead of build-time so we keep the CI/CD of each micro frontend autonomous and independent.

Web Component composition

Web components are platform independent and you can easily include any dependency. Frameworks like Vue and Angular offer out-of-the-box ‘conversion’ to web components. The API is clean and easily understandable.

Communication

How do the different micro frontend communicate with each other? Try to keep it to a minimum but when needed it’s not that different from micro services or componentized frontends. Either use custom events, perhaps in combination with an eventbus (pubsub architecture), properties (in case you are using web components) or the URL. Events/props can be combined with a state manager in the orchestrator but keep in mind that you are introducing more complexity.

Downsides of a micro front-end

So, do micro frontends finally fix all issues we have with scaling web applications and does it bring world peace? Though they bring advantages they come at a cost and in a lot of cases it’s not worth the extra overhead they require. If only one team is responsible for everything related to the frontend you might not need an orchestrator nor separate frontends for specific features.

Every micro frontend should be as stand-alone as it can be, libraries and other dependencies are part of the payload. Including frameworks like React or Vue for example results in a substantially bigger payload for the application as a whole. You could serve the framework’s code from a CDN or from a shared bundle, but those need to be versioned in case two or more micro frontends use the same framework but a slightly, incompatible, version.

Another downside is the overhead needed to develop and run the micro frontends while developing. Perhaps a blank orchestrator is needed, or the micro frontend is run in stand-alone mode allowing you to bypass authentication and navigation needs provided by the orchestrator, or a different micro frontend. This brings an extra layer of code and thus complexity, partly because the micro frontend was never intended to be stand-alone.

More frontends lead to more repositories, more CI/CD pipelines, more tools, more servers, more of everything.

Consider if these trade-offs are worth the investment and consider the expectations because not all applications will increase in size or complexity as time goes by. Just as not all websites are web applications, not all web applications need a micro frontend architecture.

Conclusion

Creating scalable, maintainable frontends is very hard. Where backend systems have been broken up into many smaller micro services years ago, many frontends are still built as a monolith.

Micro frontends are about slicing up monoliths into more manageable pieces and being more explicit about the dependencies. By being independent the development can be more feature driven and less focussed on release cycles.

Micro frontends bring smaller and more maintainable code but including frameworks like React or Vue for example results in a substantial bigger payload for the application. Another downside is the overhead needed to develop and run the micro frontends while developing. Perhaps a blank orchestrator is needed, or the micro frontend is run in stand-alone mode allowing you to bypass authentication and navigation needs provided by the orchestrator or a different micro frontend. More frontends lead to more repositories, more CI/CD pipelines, more tools, more servers, more of everything.