Microfrontends: Microservices for the Frontend

Microfrontends: Microservices for the Frontend

Can we take microservice architecture patterns and apply them to the frontend?

Featured on Hashnode

Microfrontends: Microservices for the Frontend

Microservices are a popular way to build small, autonomous teams that can work independently. Unfortunately, by their very nature, microservices only work in the backend. Even with the best microservice architecture, frontend development still requires a high degree of interdependence, and this introduces coupling and communication overhead that can slow down everyone.

Can we take microservice architecture patterns and apply them to the frontend? It turns out we can. Companies such as Netflix, Zalando, and Capital One have pushed the pattern to the front, laying the groundwork for microfrontends. This article will explore microfrontends, their benefits and disadvantages, and how they differ from traditional microservices.

Microservices for the frontend

Microfrontends are what we get when we bring the microservice approach to the frontend. In other words, a microfrontend is made of components — owned by different teams — that can be deployed independently. These components are assembled to create a consistent user experience.

From left to right we show 4 scenarios. Crossing all scenarios, a middle horizontal line splits the frontend from the backend. On the left side, we have a monolith that crosses the front and backend (including the UI). Next, we have a monolith on the backend and an SPA webpage on the frontend (the UI is separated from the backend). On the third position, we have the same SPA on the frontend, but the monolith has been split into microservices on the backend. The final scenario uses microservices on the backend and a microfrontend on the frontend. The frontend comprises different components or widgets isolated from the rest.
A monolith can be decomposed in different ways. We can split frontend and backend or use microservices on the backend. We can even recreate the frontend as a collection of isolated components managed by different teams.

With a microfrontend, no single team owns the UI in its entirety. Instead, every team owns a piece of the screen, page, or content. For example, one team might be responsible for the search box, while another might code suggestions based on users’ tastes. Additional teams might code the music player, manage playlists, or render the billing page. We add complexity but teams get increased autonomy in return.

A wireframe of a music streaming website. There are 5 teams in total: one for the ‘homepage’ itself, one for the playlist, one for the recommendation, one for the search box, and one for the music player. Each team has assigned a corresponding widget or component within the homepage.
Frontend functionalities are managed by different teams, independently deployed, and injected into the homepage transparently to the end user.

Benefits and challenges of microfrontends

Microfrontends offer similar benefits to microservices. Namely, we can scale up development by breaking the frontend code into isolated pieces, for which different teams are responsible. As with microservices, each feature can be released on its own, at any time, with little coordination. This leads to more frequent updates.

Vertical teams

Microfrontends enable the creation of vertical teams, which means a full-stack team of developers can own a feature on both the backend and the frontend at the same time.

Vertical teams handle all the features and code for a given feature or component. We have 3 teams: suggestions, search, and playlist. Each team manages its microservice backend and microfrontend independently of the others.
Fullstack vertical teams are responsible for both the frontend and backend of a feature or component.

Continuously deployable components

Every part of a microfrontend is a deployable unit. This allows teams to publish their changes without waiting for a release train or depending on other teams finishing their work. The end result is that the frontend can be updated several times per day.

Each team has a separate source repository, CI/CD pipeline, and production service, which serves the microfrontend content. The composition of all the separate microfrontends renders one frontend on the client.
Each team can have separate repositories, CI/CD pipelines, and service machines. Alternatively, we can host everything on a monorepo and have a single shared CI/CD pipeline.

Challenges of microfrontend design

The main challenge of microfrontends is creating a fast and responsive client. We must never lose sight of the fact that the frontend lives in an environment with limited memory, CPU, and network, or we risk ending up with a slow UI.

A snappy UI is vital for the success of the product. A recent survey pointed out that “a site that loads in 1 second has a conversion rate 3x higher than a site that loads in 5 seconds”. Every second the user has to wait, money is thrown out of the window.

In addition to all the challenges microservices have, microfrontend design poses a few more problems:

  • Isolation: each team’s code must eventually coexist on the same browser. We must be deliberate in isolating the separate modules to avoid collisions of code or style.
  • Shared resources: to avoid duplication and keep the frontend thin, components should share assets and libraries when possible, which may create undesirable coupling.
  • Accessibility: heavy reliance on JavaScript to render the page negatively affects accessibility.
  • Styling: When the UI is made up of components produced by various teams, maintaining a consistent look is more complicated. Small stylistic inconsistencies can feel jarring.
  • Coordination: with so many moving parts, APIs need to be very well-defined and stable. Teams must coordinate on how different components in the microfrontend communicate with each other and the backend microservices.

Principles for building microfrontends

There are two complementary methods for rendering a unified UI from separate microfrontend components: server-side and client-side rendering.

Server-side rendering (SSR)

Server-side rendering offers faster performance and more accessible content. Rendering on the server is a good alternative for serving content quickly — especially on low-power devices like low-end phones. It is also a suitable fallback mode when JavaScript is disabled.

Server side rendering schematic. Various microservices are polled by a webserver. They reply with HTML fragments that the webserver assembles and forwards to the user’s browser.
The webserver assembles the full page from the content provided by different microservices. This serves as the first contentful page. Hydration can then be used to add more dynamic content.

We have a few ways of performing SSR:

  • Server Side Includes (SSI): is a simple scripting language executed by the webserver. The language uses directives to build HTML fragments into a full page. These fragments may come from other files or the responses of programs. SSI is supported by all major webservers, including Apache, Nginx, and IIS.
  • iframes: the venerable iframe feature allows us to embed arbitrary HTML content on a page.
  • Edge Side Includes (ESI): a more modern alternative to SSI. ESI can handle variables, have conditionals, and supports better error handling. ESI is supported by caching HTTP servers such as Varnish.

So, for instance, we can use SSI to render a page from HTML with this:

<div>
  <!--#include virtual="/hello-world" -->
</div>

The virtual keyword makes the webserver request the content from a URL or CGI program. In our case, we would need to set up the webserver to respond to requests based on the path /hello-world with a suitable fragment:

<h1>Hello World!</h1>

SSR is used in many web frameworks to render the first screen. Additionally, there are also some interesting SSR-specific utilities like compoxure, nodesi, and tailor.

Client side rendering (CSR)

Client-side rendering builds the page in the user’s browser by fetching data from microservices and manipulating the DOM. Most web frameworks use some form of CSR to improve the user's experience.

Client side renreding schematic. The user’s browser downloads the page source from the CDN. On-load, the page loads data from different microservice endpoints and renders the view dynamically.
CSR dynamically renders the page on the user’s browser using data provided by the endpoints.

The main tools we have to write loosely-coupled components are Custom Elements. Custom elements are part of the HTML standard. They allow us to create new HTML tags and attach logic and behavior to them.

Custom elements are dynamically mounted and unmounted from the page using JavaScript:

// hello-world-component.js

class HelloWorld extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `<h1>Hello world</h1>`;
  }
}

customElements.define('hello-world', HelloWorld);

Once defined, we can use the new element like any other HTML tag:

<hello-world></hello-world>

In the example, the full page would include a script tag to fetch the JavaScript components:

<!doctype html>
  <head>
    <meta charset="utf-8">
    <title>Microservice Example</title>
  </head>
<body>
 <script src="./hello-world-component.js" async></script>
 <hello-world></hello-world>
</body>

While most frontend frameworks can be used for microfrontends, some have been designed specifically for them:

  • Piral: implements isolated components called pilets. Pilets are modules that bundle content and behavior.
  • Ragu: a framework of frameworks. It allows us to embed code written in any framework as widgets.
  • Single SPA: a meta-framework for piecing a UI together using any combination of frontend frameworks like React, Angular, and Ember, among others.
  • Frint: another modular framework for building component-based applications. Integrates with React, Vue, and Preact.
  • Module Federation: a WebPack plugin to create Single Page Applications (SPAs) by bundling separate builds. These builds can be developed independently of each

Conclusion

Switching to a microfrontend architecture can give our development teams more autonomy, thereby accelerating development. However, the same caveats that apply to microservices are also relevant for microfrontends. We need to have a proven design, which means microfrontends are not a good fit for greenfield projects.

New projects are best served by traditional patterns such as single page applications (SPAs) managed by a single team. Only once the frontend has stood the test of time can we consider microfrontends as the way forward.