The Problem with Micro Frontends

Don't carelessly buy into the hype (hope you haven't already…)

Jose I Santa Cruz G
Stackademic

--

It's really frustrating when your blocks are not connecting with each other

The micro frontend concept is not new, but the hype isn't over. Think in ye olde jQuery days. In those painful times there was a jQuery plugin for almost anything, and many of us came to think on "What about if we could make an internal (or so) mini application to solve a single requirement, instead of building a whole webapp". And, when keeping your development constrained into a single framework or library this may be possible and it can even work without many problems. The problems come with the current state of micro frontends.

What's a micro frontend?

Remember microservices? When you had zillions of "small backends" each one thought to solve a single requirement, each of them deployed in one of the zillion small servers that fed the gargantuan app you were working on. And all of them hosted on a "microservice friendly cloud", most likely with a serverless architecture with an extremely obscure pricing tiers (no wonder the first month was a huge 3 significant zero surprise).

Yes, I'm not on microservices, at least not the way I've been forced to face them, and certainly not the way I've seen how this kind of architecture has been built in some projects. Not saying it's by itself, but without the proper analysis it will most likely be a disaster.

Micro frontends are "the client side version of microservices" (BTW this is a really bad definition but helps us in an initial understanding). Small apps, aimed to solve a quite restricted problem or requirement, and placed inside a container app. Small apps that are NOT constrained to a single framework, o even worse, not constrained to the same version of a specific framework.

And many organizations are falling, or already fell, on the micro frontend hype, just the same way many did on the microservices hype:

"All big companies are doing so, we also should."

What's the problem with micro-whatevers?

"Can we have ice cream instead of pumpkin pie for dessert?"

In an improvised family reunion, have you ever tried to make any decision that makes everybody happy? Like what movie should we watch, or what dessert should we prepare.
Statistically, in any group of more than 5 persons, it's not simple to agree on anything (unless your mother decides for the dessert option and no movies).

Well, the same goes for both microservices and micro frontends. The biggest pain is coordinating between all your micro-somethings and "making them speak between each other".

On microservices we can say this problem is solved, there are far too many libraries, frameworks and tools to make your microservices ecosystem work as a whole.
Micro frontends is a completely different story. There're not so many frameworks aimed to simplify working with micro frontends, in fact, only Single SPA comes to my mind right now. And sadly, most implementations I've faced come from the architect's creative state of mind when all core definitions where engraved on stone. (As always, your mileage may vary, this is coming from my own pain and experience).

Complexity

Micro frontends be like…

When the solution for refactoring a web application seems to be dividing it into several micro frontends, it is most likely because it became too complex to stand as a single webapp.

Why refactoring?

Micro-whatevers don't usually (usually…) come as the first choice.
The implementation of a microfrontend solution comes from noticing an already existing monolithic webapp started to grow, and also started to get out of control, too many dependencies, too complex to maintain, too many mixed domains.

So the most natural approach is to divide an conquer, turn your giganormous webapp into smaller, hopefully independent, well developed webapps.

On paper it seems "simple", well defined domains should lead to as many smaller webapps, SOLID principles would be respected, everything will go fine. It will take some time, but refactoring is the way of achieving a performant and scalable webapp.

What can we be missing?

Image borrowed from https://medium.com/nerd-for-tech/the-power-of-message-orchestration-b28f18da603a#95bd

Complexity = n(n-1)/2 ; this article regarding message orchestration explains it very well. The more components you have in your app, the more complex it becomes, in a non-linear way.

Imagine we mix this up with a not so experienced team, that would be a terrible combination.

How are micro frontends included in a webapp?

Completly discarding embedding a child app inside the parent app using frmaes o iframes, the answer is including the child app's script in the parent's app DOM.

Underneath the sheets, this would be something like:

<html>
<!-- ... inside the parent's app DOM -->
<script src="child_app.js"></script>
</html>

And it can be done directly inside the HTML file, or using some other script to do so. And if you're using any framework, all this dirty work will be hidden from your eyes, you just have to make it work.

Most of the times, micro frontends can be disguised as web components, and the parent app has to do all the heavy lifting to include them as a part of the application.

Communication between micro frontends

Communication is probably the biggest pain on micro frontends, how do we make them speak with each other? Thinking on what's common between multiple sibling micro frontends deployed under a same parent app , what's common? And after a 2nd thought you may have guessed right "the browser".

So let's use the browser, and here's what is common between multiple scripts hosted on the same sub-domain and port, running under the same browser window and hopefully under the same tab:

  • Browser's window object
  • Service workers
  • Browser's application storage (localStorage, sessionStorage, indexed DB, web SQL)
  • and others I'm probably missing in this list

And if things are going to happen on different tabs, your best choice to get all tabs "synchronized" is to use the Broadcast Channel API.

But disregarding what's your preferred poison of choice, it's not magic, you'll have to implement a way of receiving some kind of message/signal in order to respond to it executing whatever code is required.
While you actually can call a shared script function, it's operation has to be isolated from every particular microfrontend's context, otherwise screwing things up is likely to happen (and we don't want that 😬).

Perhaps you spotted some clues:

  1. A message/signal has to be sent
  2. The message/signal has to be captured
  3. Some action has to be executed

And that's really near the pub-sub design pattern.

Pub-sub design pattern
Pub-Sub image borrowed from https://www.enjoyalgorithms.com/blog/publisher-subscriber-pattern

When I faced this problem, I came up to this article: Cross micro frontends communication , and after making some tests, I totally agree that the windowed-observable library is a great way of solving this problem (I just don't like the name collision on windowed-observable's Observable and RxJS Observables, but that can be solved using an import rename).

Routing

What if your microfrontend requires some internal routes to navigate into different internal pages and functionalities?
Remember that all your micro frontends can be using their own frameworks, or even different versions of the same one. And most frameworks have their own implementation of a routing library.

Routing is one of those problems that will keep you banging your head against the keyboard for a while. You may be tempted to think that if all micro frontends stick to the same framework and the same library this won't be a problem. Sad truth is that even though all micro frontends are contained within the same parent app, all micro frontends respond to their own execution context, and again communication with the outer-world appears as a problem: the parent app has to know that internal micro frontend routes don't require a full app reload, child-apps (micro frontends) have to delegate the routing handling to the parent-app.

This usually leads to inconsistent routes between the app and the browser's location bar, or links not working at all.
In Angular you can play around with all Router Events, but again, micro frontends are contained in a parent-app but live within their own context, meaning they also have their own routing. And so does the parent-app. So, unless your implementation can ensure that routing will behave as expected between child-apps and the parent-app, this can easily become a PITA.
And I absolutely don't recommend messing up with the Window.history API, and faking the navigation without changing the actual URL, too much damage con be done and will get you running in circles.

Sadly I don't have a non-opinionated solution for this. My approach would be generating custom event-based routing actions, and triggering the real navigation event, implementing a pub-sub strategy. But I really don't like to focus on the problem as if it was a nail, when the only tool I know seems to be a hammer.

Bundle sizes

Micro frontends are developed as single independent web apps, supposed to be small, because of the specificity of their domain. Small webapps, right?

OK, let's add the container/parent-app bundle size. And how about adding all other micro frontends. Get where I'm getting to?
For example an Angular app, built for production, optimized, minified, tree-shaked, without any compression can be between 700Kb and 2Mb. Lucky for us that text files compress quite well, and using Brotli we get pretty good compression rates.

Micro frontends have to be built as a single bundle, otherwise they won't include everything that's required. This includes the framework and all the required libraries, tree-shaked of course.

But still, a large and complex application bundle shouldn't exceed 3Mb, or less. At least the initial bundle.
Some will say that "bundle size doesn't matter with current broadband speeds" or thta "bundle sizes don't matter when dealing with an internal use application". But users DO MIND if the app takes more than 5 seconds to be usable.

What if the app is sticking to the same framework AND the same specific version? Every single bundle will include the same core libraries. Unnecessary downloading the same libraries again and again IS a problem.
Nobody wants to use a slow starting application, neither downloading more than 30Mb just on scripts.

Module federation is thought to solve this problem. On my experience with Angular micro frontends, using Angular Elements and Externals has been a little simpler, but I'll leave that choice to you.
Also made some research on using Webpacks DLLs, but I didn't get to any conclusive result.

Deployment

Just because being a micro-something, micro frontends deployment is thought to be done "one server per micro frontend", ala microservice style. This can add another level of complexity to this already complicated equation.

Most implementations delegate the script finding to an API gateway, but on excessively creative scenarios it can require some hand configuration over the parent's app http server, proxying each script request from the parent-app so it really retrieves the script from the correct server.

Proxy configuation diagram for micro frontends

Proxy configuration can get messy and large as the micro frontend universe grows for the parent-app. Think about how maintainable this will be in the future, scalability is not just the ability to grow, but also controlled growth.

Integration, testing and Developer Experience (DX)

Ideal world, every developer is happy working 💻

Micro frontends are developed as single we applications. They are supposed to cover a single domain, solve single or related requirements, and implement related features, always constraining them within a ̶v̶e̶r̶y̶ ̶w̶e̶l̶l̶ ̶d̶e̶f̶i̶n̶e̶d̶̶ defined context.

What if we have to make any kind of integration between different micro frontends? And by different this can also mean that they are maintained by another development teams.

Depending on the type of integration, this can require a fake parent-app, or even a way of live/hot reloading container in which we can check our changes without the need of deploying our micro frontend to a shared development environment.

10If the architecture team didn't consider this, as the development environment is shared, any breakages in it may become a blocking issue. Seen this on a micro frontend based app developed by several remote teams located all over the world, with different timezones.

Testing can actually be done locally, but unless the container parent-app is shared between every micro frontend team, besides deploying to a shared development environment the there's no way of testing how our micro frontend will behave when integrated into the parent-app (GOTO 10).

In a monorepo styled project this is not likely (not likely…) to happen, as every team can access the code for every micro frontend, including the container/parent-app, and keeping all projects safe from non-authorized modifications is not so hard to achieve (git subprojects, git hooks, or any other more creative strategies).

If this new micro frontend salad project is big enough to require several development teams, each one dedicated to fully working on their own child-apps, or even if your project is being maintained by a single team, the Developer eXperience is a MUST.
If developers feel confortable working in the project, and don't face more pains than the usual, deadlines will be met, issues will be solved in their expected Sprint, the client will be happy, the manager will be happy, and if everyone's happy the project is supposed (supposed…) to run smoothly.

The Technical darkness linked to an extreme jelousy on how the inner guts of a system work and integrate between each other are also harmful for the DX, but that's not on micro frontends but on project management.
Conway’s law is harsh truth, that's appliable in both project management and all micro frontend pains:

Any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization’s communication structure.

— Melvin E. Conway (on 1967)

Micro frontends, just as microservices, are not intrinsically bad per se, as they WILL BE the best choice for solving a certain kind of problems. All problems come when, at the moment of deciding to adopt this architecture, all the known related difficulties and drawbacks are just ignored, and the project continues to move forward as a Mad Max battle truck.

Thanks for reading! Most of this article is based on my own pains when dealing with micro frontend architectures. Do your own research, build up your own opinion on this, but please don't fall carelessly on the hype.

Stackademic

Thank you for reading until the end. Before you go:

  • Please consider clapping and following the writer! 👏
  • Follow us on Twitter(X), LinkedIn, and YouTube.
  • Visit Stackademic.com to find out more about how we are democratizing free programming education around the world.

--

--

Polyglot senior software engineer, amateur guitar & bass player, geek, husband and dog father. Not precisely in that order.