Micro frontends have become a hot topic in the tech world in recent years. When I started researching and applying this technology, it turned out it wasn't as simple as I had initially thought. That’s mainly because there are so many advanced concepts we need to deal with to build a good micro frontend.
But where do you even start? There are many tutorials and articles on the internet on micro frontends. However, I could not find any articles covering everything I needed, explaining the basics of micro frontends, their purpose and the best tools to build one.
So, I decided to write this post to give a comprehensive overview of micro frontends that covers all core aspects of the technology.
What is a micro frontend and why do we need it?
The term micro frontend was introduced in 2016 and its usage has continued to gain in popularity ever since. Basically, it extends the concepts of micro services to the frontend world. As you know, a multi-page app/single-page app is built by combining many blocks/components. Over time, the frontend layer is often developed and maintained by different teams, causing it to grow and become more difficult to maintain. These concerns are exactly why many companies continue to struggle with monolithic frontend codebases.
To deal with these issues, the idea of micro frontends was born, allowing different micro-apps within a larger app to have their own frontend. This brought with it a lot of advantages:
- They allow teams to work independently and just take care of their distinct area of business.
- They allow us to work with a smaller and maintainable codebase.
- They enable us to upgrade or even rewrite the path of the frontend easily and safely, since each application will contain less code and not depend on other apps.
- They can be deployed autonomously without fear of impacting another team.
- They allow us to easy scale individual services, with each application using its own scale requirements.
A micro frontend architecture diagram
How to build a micro frontend?
Now we know what a micro frontend is, so let’s take a look at how you build one. This is where it can get a bit tricky. There are a lot of advanced concepts we need to deal with, and now I will walk through each challenge one by one.
Integration approaches for a micro frontend
There are 2 main micro frontend integration approaches:
Build-time integration:
The build-time integration approach focuses on merging micro frontends during the application building phase. Here, each micro frontend is created separately and then compiled into a collective library or bundle. This collective resource is then utilized by the main application during the process. This guarantees synchronization and optimization of all micro frontends during compilation, producing a single, unified package.
However, there’s a downside to build-time integration in micro frontends. The problem is that it can lead to duplicate bundles of code. Plus, getting rid of code that’s not being used can be a tough task.
Runtime integration:
This approach focuses on loading the micro frontend code at runtime, after the container application has already loaded in the browser. Each micro frontend has its own entry point, and the container application is responsible for notifying them where and when to render their content on the page. This offers greater flexibility and independent deployments for micro frontends but requires more complex communication and coordination between them.
One challenge with the runtime integration approach in micro frontends is what’s known as the ‘waterfall’ effect. This happens when one part of the website (a micro frontend) can’t start loading until another part has finished loading. It’s like a line of dominoes where each one has to fall before the next one can start. This can slow down the loading of the entire website, which isn’t ideal for user experience.
Here are some additional variations within these approaches:
- Iframe is an old approach to use single-page with multiple inner applications, each one in a different iframe.
- Webpack Module Federation is a Javascript architecture invented by Zack Jackson. This architecture allows a Javascript application to dynamically load code from another application and, in the process, share dependencies, which allows for less code duplication in the application. It only works on webpack 5.
- Import-map is a strategy using the single-spa framework, allowing control over what URLs get fetched by Javascript import statements and import().
- Micro-Frontend using Web Components, where each application can be represented by a custom HTML element.
Selecting the right micro frontend framework
There are 7 micro frontend frameworks. I don’t want to list all of them here, so I only cover the framework I find best suited to help us build great micro frontends.
- single-spa: single-spa is a framework for bringing together multiple JavaScript micro frontends in a frontend application.
- Luigi: powered by SAP, it uses iframes to build micro frontends.
- Piral: Piral enables you to create a modular frontend application that is extended at runtime with decoupled modules called piletsleveraging a micro frontend architecture.
- Frint: a complete framework that delivers routing, state management, server rendering and other features to micro frontend architecture.
- Bit lets you compose and manage frontends from independent components. It’s probably the most popular and production-ready solution on the list.
- Webpack 5 and Module Federation: Multiple separate builds form a single application.
I suggest that you use webpack 5 and module federation to set up micro frontends since it makes the code independently deployable between applications and makes code sharing and state management easier.
Styling and theming a micro frontend
Share a style guide
As we all know, the UI of the parent app is made up of multiple micro-apps, so we have to ensure user experience and look and feel are consistent. Therefore, I want to emphasize that the style guide and design system must be shared between team.
Learn more on how you can benefit from UI/UX design for your micro frontend architecture design.
How to manage style in micro frontends effectively while improving performance
We can share the same style at the global level. However, sharing CSS globally can introduce risk to the class conflict.
- We can apply a newer approach by using CSS-in-JS (library styled-components) so that we can add, change, and delete CSS without any unexpected consequences. This sounds great, but unfortunately, when I did a comparison, I noticed that the styled CSS-in-JS implementation appeared to take about 40% longer to render the critical path of a web page, this is a critical issue that we can't ignore. This approach leads to JavaScript bundles being heavier – especially when we access the web app on devices and from regions with slower internet connections, the large JavaScript bundle will lead to slow downloads and blocked rendering
So, what do we do to solve this issue? This can be accomplished with some different approaches:
- Using BEM (Block Element Modifier) is a methodology that helps you to create reusable components and code sharing in frontend development.
- Using MiniCssExtractPlugin to extract CSS into separate files to reduce the size of your JavaScript bundle file. It creates one CSS file per JS file that contains CSS and supports on-demand loading of CSS. This is still not enough, because if we load external stylesheets as async, it leads to a flash of unstyled content. But if we load style as synchronized, then it blocks the first paint of a webpage. That means we only use non-critical CSS as external stylesheets and download as async, and deliver critical CSS as inline style at the head tag.
Manage theming of your micro frontend
In the past, we have often developed a web app intended to be used by dozens of brands - which all require different colors and branding. For some requirements, we need to switch between themes instantly on the webpage without refreshing the page, but for others, we only need to set up a theme from initial load. So how do you control the theming in a micro application? Let’s walk through some options:
- Using CSS custom properties as variables: This is the new CSS feature known as Custom Properties. If we have a lot of themes, we should not define override variables for each of them because it leads to a large CSS bundle. Therefore, we should re-assign variable values with JS.
- Using mutating state: In a reactive framework, we can store CSS properties such as colors, background etc. as a part of state. Then, we can fill the state value of properties to class and use style bindings or event-scoped CSS.
Shared component libraries
In micro frontend architecture, we need to share some common UI components across child and parent applications. Some candidate components for sharing are buttons, checkboxes, labels or selections. We can also share more complex components such as suggestions and pagination. However, make sure that your shared components include only UI logic, not business or domain logic. When domain logic is put into a shared library, it introduces a strong coupling across applications and increases the difficulty of change.
Furthermore, we want a solution that can share dependencies. That means we should never load the same dependency twice.
In order to deal with the problems above, we can use Webpack 5 and module federation. This plugin helps expose and share components in both build time and run time and is considered to be the most popular approach.
State management
State management can be challenging when it comes to micro frontends. Which state manager to use, what state to store between the different micro frontends, how to let them talk to each other, how to architect it all – there are many questions to be answered. We can share states using 4 different methods:
- Web Workers
- Props and callbacks
- Custom Events
- Pub Sub library (windowed-observable)
I prefer windowed-observable because it is simple to set up, pretty customizable, isolates namespace events, and offers extra features to retrieve dispatched events.
Handle routing
I strongly recommend that each team building a micro-app is responsible for routing within its own web app. If one team updates a router map, it will need to talk to all teams.
Conclusion
In this article, I covered a number of technical considerations to help you design and develop your micro frontend architecture.
There are many ways to accomplish the same goal as long as your app has to meet some criteria like Core Web Vitals scores, UX Style Consistency, Cohesive User Experience, or Smooth Inter-App Transition.
Which style of creating micro frontends do you prefer? Let us know below or contact us today to find out more about how we can help you build your micro frontend.
Frequently Asked Questions about micro frontends
What is a micro frontend shared component library?
A shared component library refers to a collection of UI components for web applications that are utilized across various micro frontends. These components are adaptable to different modern programming languages, yet it's recommended to opt for one that is straightforward to grasp and recognized by people working on your project.
What is an example of a micro frontend?
Let’s imagine a regular website that includes various sections like the homepage, About Us, Services, Checkout, Payment, etc. Applying the micro frontends idea, each page could be built as a single, autonomous application. This concept also allows for the division of software development teams into specialized groups.
What is the difference between microservice and micro frontend?
Micro frontend is used when you need to deal with frontend complexity and accommodate multiple development teams, while microservice is for establishing a scalable and modular backend architecture. These approaches can work in tandem when constructing a comprehensive, independent, and adaptable system.
What is build-time integration vs. run-time integration?
Build-time integration, a conventional method for sharing code, involves installing libraries from a package manager like npm and utilizing them throughout an application. This method requires all dependencies to become part of the application bundles, leading to an increase in app size with each newly added dependency. In contrast, run-time integration provides a more effective approach to code and functionality sharing. In this model, each micro-frontend can be constructed and implemented independently. Typically, a host application takes on the responsibility of rendering remote applications by fetching their bundles at runtime when required.