
Micro Frontends
December 2023
An architectural style where independently deliverable frontend applications are composed into a greater whole
The concept of micro frontends evolved organically rather than being the invention of a single individual. It emerged as a natural extension of microservices architecture, adapting its principles to frontend development. Microservices were popularized by companies like Netflix and Amazon, which demonstrated the effectiveness of breaking down backend systems into smaller, independently deployable services.
The term "micro frontends" started gaining traction in the tech community around 2016-2017. Thought leaders and practitioners in software development, such as Martin Fowler and others, began discussing and formalizing the concept.
- Business domain representation
- Autonomous codebase
- Independent deployment
- Single-team ownership
Core concepts
by Luca Mezzalira
- Defining what a micro-frontend is in your architecture
- Composing micro-frontends
- Routing micro-frontends
- Communicating between micro-frontends
Micro-frontend decision framework
by Luca Mezzalira
Build-Time Integration
Definition: In build-time integration, micro frontends are combined into a single application bundle during the build process. This happens before the application is deployed.
Advantages:
- Performance: Since the application is compiled into a single bundle, it often loads faster for the end-user.
- Simplified Deployment: The entire application is deployed as a single unit, simplifying deployment processes.
- Consistent Dependencies: Dependencies are resolved during the build, ensuring consistency across the application.
- Reduced Runtime Complexity: There’s less runtime overhead because the integration complexity is handled during the build phase.
Build-Time Integration
Definition: In build-time integration, micro frontends are combined into a single application bundle during the build process. This happens before the application is deployed.
Challenges:
- Reduced Flexibility: Teams lose some independence, as changes in one micro frontend might require a full rebuild and redeployment of the entire application.
- Complex Build Process: The build process can become complex, especially as the number of micro frontends increases.
- Versioning Issues: Managing versions of individual micro frontends can be challenging, as they are all bundled together.
Run-Time Integration
Definition: In run-time integration, micro frontends are assembled during the execution of the web application in the user's browser. Each micro frontend is loaded independently.
Advantages:
- Team Independence: Teams can develop, deploy, and update their micro frontends independently without needing to rebuild the entire application.
- Dynamic Loading: Different parts of the application can be loaded dynamically, improving initial load times and enabling more interactive experiences.
- Flexibility in Technology Stacks: Different teams can choose different technologies and frameworks for their micro frontends.
Run-Time Integration
Definition: In run-time integration, micro frontends are assembled during the execution of the web application in the user's browser. Each micro frontend is loaded independently.
Challenges:
- Performance Overhead: Loading multiple micro frontends at runtime can increase the overall load time and impact performance.
- Complexity in Integration: Coordinating and integrating different micro frontends at runtime can be complex, especially regarding shared dependencies and state management.
- Consistency Issues: Ensuring a consistent look and feel and user experience across different micro frontends can be more challenging.
Iframe
Using iframes is one of the techniques to implement micro frontends, especially when isolation and independence are key concerns. An iframe, short for inline frame, is an HTML element that allows an external webpage to be embedded within the current page. Here's how iframes are used in the context of micro frontends:
Advantages of Using iframes for Micro Frontends
-
Isolation: iframes provide a strong level of isolation between the host page and the embedded micro frontend. This isolation includes JavaScript execution, CSS styling, and local storage, which helps prevent conflicts and ensures security.
-
Independence: Each micro frontend can be developed, deployed, and operated independently. Changes in one micro frontend do not affect others, as each is loaded in its own iframe.
-
Technology Agnosticism: Teams can use different technology stacks for each micro frontend, as the iframe acts as a container that doesn't interfere with the host page's technology.
-
Simplified Integration: Embedding a micro frontend is as simple as pointing the iframe's
srcattribute to the URL of the micro frontend. This reduces the complexity of integration compared to other methods.
Challenges of Using iframes
-
Performance Overhead: Loading a webpage in an iframe can be slower compared to other integration methods, as each iframe is essentially a separate webpage with its own resources and assets.
-
User Experience Consistency: Ensuring a consistent look and feel across the host page and the iframes can be challenging. Additionally, navigation and interactions might feel less seamless compared to a single-page application.
-
Cross-iframe Communication: Communication between the host page and iframes or between different iframes can be complex, often relying on postMessage API or similar mechanisms. This can add overhead and complexity in handling data exchange and synchronization.
-
SEO and Analytics Considerations: Iframes can complicate search engine optimization (SEO) and analytics, as search engines might treat the content in iframes differently from the main page.
-
Accessibility: Making iframe content accessible can be challenging, as screen readers and other assistive technologies may not always interact well with iframes.
Module Federation
Module Federation is a feature provided by Webpack 5 and recently by Vite as well. It allows for the dynamic sharing of code and functionality between separate and independently deployed JavaScript applications at runtime. This feature is particularly relevant in the context of micro frontends, as it enables different frontends (or parts of a frontend) to function as standalone applications yet still share code seamlessly.
Module Federation
Key Concepts of Module Federation
-
Host and Remote: In Module Federation, there are typically two roles:
- Host: The application that loads code from another application.
- Remote: The application that exposes parts of its code to be consumed by the host or other applications.
-
Dynamic Code Sharing: Unlike traditional static imports, Module Federation enables dynamic loading of code from a remote application. This means that the host application can import modules from a remote application at runtime.
-
Version Management: It allows different versions of the same shared modules to coexist in the same application, helping to avoid version conflicts.
-
Isolation and Independence: Each part of the application (whether host or remote) can be developed, deployed, and operated independently, reducing the risk of changes in one part affecting the others.
Module Federation
Key Concepts of Module Federation
How Module Federation Works
- A remote application exposes certain modules or components through its Webpack configuration.
- The host application then consumes these exposed modules by specifying them in its own Webpack configuration.
- When the host application runs, it dynamically loads the exposed modules from the remote application over the network.
- This setup allows for the sharing of functionality, like utility libraries, components, or even entire sections of an application, without needing to bundle them together at build time.
Module Federation
Configure the Remote: In the Webpack configuration of the remote application, specify which modules to expose.
// webpack.config.js of Remote
plugins: [
new ModuleFederationPlugin({
name: 'remoteApp',
filename: 'remoteEntry.js',
exposes: {
'./MyComponent': './src/MyComponent',
},
shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
//shared: ["react", "react-dom"]
}),
]Module Federation
Configure the Host: In the Webpack configuration of the host application, specify where to find the remote application's exposed modules.
// webpack.config.js of Host
plugins: [
new ModuleFederationPlugin({
remotes: {
remoteApp: 'remoteApp@http://example.com/remoteEntry.js',
},
shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
}),
]Module Federation
Consume the Exposed Module: In the host application, import and use the exposed module as if it were a local module.
// In a component of the Host application
import MyComponent from 'remoteApp/MyComponent';
function App() {
return (
<div>
<h1>Host Application</h1>
<MyComponent />
</div>
);
}But what is Webpack btw?
Webpack is a powerful and popular open-source JavaScript module bundler, but its capabilities extend far beyond just bundling. It's a tool that's primarily used to compile JavaScript modules, and it has become an essential part of the web development workflow, particularly in complex applications. Here's a detailed explanation of Webpack:
But what is Webpack btw?
Basic Concept
- Module Bundler: Webpack treats every piece of your application (JavaScript files, CSS, images, etc.) as a module. It allows you to bundle these modules into one or more bundles (files) typically served to a browser.
-
Loaders: Webpack itself only understands JavaScript. Loaders transform other types of files into modules that Webpack can handle. For example,
style-loaderandcss-loaderenable Webpack to handle CSS files,babel-loaderallows you to use modern JavaScript (ES6+) by converting it into a format that different browsers can understand. -
Plugins: Plugins can be used to perform a wider range of tasks like bundle optimization, asset management, and injection of environment variables.
-
Entry Point: This is where Webpack starts the bundling process. It represents the module/file from which Webpack starts building its internal dependency graph.
-
Output: It's the bundled JavaScript file(s) that Webpack generates. The configuration specifies where to emit these bundles and what to name them.
-
Mode: Webpack can be run in different modes, namely
development,production, ornone. Each mode has a set of default optimizations. -
DevServer: Webpack provides a development server that can be used to serve web applications for development purposes, featuring live reloading.
Alternatives to webpack:
- Rollup
- Parcel
- Vite
Routing
There are many ways to aquire routing inside micro-frontends. The obvious choice would be some Nginx config.
In React environment we can use React Router Dom library
Routing
// App.js (Container Application)
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import MicroFrontendA from './MicroFrontendA';
import MicroFrontendB from './MicroFrontendB';
function App() {
return (
<Router>
<Routes>
<Route path="/micro-frontend-a/*" element={<MicroFrontendA />} />
<Route path="/micro-frontend-b/*" element={<MicroFrontendB />} />
// Add more routes for other micro frontends
</Routes>
</Router>
);
}
export default App;Container app
Routing
// MicroFrontendA.js
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import PageOne from './PageOne';
import PageTwo from './PageTwo';
function MicroFrontendA() {
return (
<Router>
<Routes>
<Route path="/" element={<PageOne />} />
<Route path="/page-two" element={<PageTwo />} />
</Routes>
</Router>
);
}
export default MicroFrontendA;Microfrontend app
Routing
Ensuring that internal links and routes in micro frontends are relative to the micro frontend's base path is crucial for correct routing, especially when these micro frontends are integrated into a larger application. Here’s how to manage base paths in React Router DOM v6 for micro frontends:
Using BrowserRouter with a basename
When you use BrowserRouter in your micro frontend, you can specify a basename. This basename is the base URL for all locations. For example, if your micro frontend is served from /micro-frontend-a, you set the basename to /micro-frontend-a.
Routing / Basepath
// MicroFrontendA.js
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
function MicroFrontendA() {
return (
<Router basename="/micro-frontend-a">
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
{/* other routes */}
</Routes>
</Router>
);
}
export default MicroFrontendA;Comunication in MF
1. Custom Events and Event Listeners
- How it Works: Micro frontends can dispatch custom events to the window object, which other micro frontends can listen to and respond accordingly.
- Use Cases: This method is suitable for simple, non-frequent events like user actions or status updates.
- Example: A micro frontend dispatches a custom event when a user logs in, and other micro frontends listen to this event to update their UI accordingly.
Comunication in MF
2. Shared Global State (State Management Libraries)
- How it Works: Using state management libraries like Redux or Context API in React, micro frontends can share a common global state.
- Use Cases: Ideal for sharing data like user preferences, authentication tokens, or application settings.
- Example: A user's login state stored in a global Redux store is accessible to all micro frontends for conditional rendering based on authentication.
Comunication in MF
3. JavaScript Functions and Callbacks
- How it Works: Micro frontends can expose JavaScript functions or accept callbacks, allowing them to directly communicate or trigger actions in each other.
- Use Cases: Useful for direct and immediate communication, like triggering a refresh or update.
- Example: A parent container passes a callback function to a micro frontend, which it calls to notify the parent about certain events.
Comunication in MF
4. Web Components
- How it Works: Encapsulating micro frontends as web components allows them to communicate via properties and events, maintaining encapsulation and independence.
- Use Cases: Best for creating self-contained, reusable components that need to interact with various parts of an application.
- Example: A web component-based micro frontend emits a custom event when its internal state changes, which the container or other micro frontends can react to.
Comunication in MF
5. URL and Query Parameters
- How it Works: Using the URL and query parameters for passing data between micro frontends.
- Use Cases: Suitable for passing simple, stateless data like user IDs or specific page states.
- Example: A micro frontend reads the user ID from the URL and fetches user data accordingly.
Comunication in MF
6. Server-Side Communication
- How it Works: Micro frontends communicate indirectly by making calls to a backend server, which acts as an intermediary.
- Use Cases: Ideal for complex or sensitive data operations, and when data consistency is crucial.
- Example: One micro frontend updates user data on the server, and another micro frontend fetches this updated data.
Comunication in MF
7. Message Bus or Event Broker
- How it Works: Implementing a message bus or event broker to facilitate communication between micro frontends.
- Use Cases: Suitable for applications with complex communication needs where events are frequent and varied.
- Example: Micro frontends publish and subscribe to various topics on a message bus, enabling them to react to specific events or data changes.
Comunication in MF
Best Practices and Considerations
- Decoupling: Keep micro frontends as decoupled as possible. Overly tight coupling can lead to a fragile system and defeat the purpose of using micro frontends.
- Consistency: Ensure that the method of communication is consistent across micro frontends to avoid confusion and complexity.
- Security: Be cautious with sensitive data, especially when using global events or shared state.
How to start with MF?
- Big Bang
- Strangler pattern
- Reverse Strangler pattern
How to start with MF?

How to start with MF?
The only thing a Big Bang rewrite guarantee is a Big Bang.
M. Fowler
How to start with MF?
The Strangler Pattern, named after the strangler fig that gradually envelops and replaces a host tree, is a method used in software development for incrementally migrating a legacy system to a new system. In the context of micro frontends, this pattern is particularly useful for gradually transitioning a large, monolithic frontend into a more modern, scalable set of micro frontends. This approach allows for a smooth and progressive replacement rather than a risky and disruptive big-bang overhaul.
How the Strangler Pattern Works in Micro Frontends
-
Identify Replaceable Parts: Begin by identifying parts of the existing monolithic frontend that can be replaced with micro frontends. These parts could be specific features, pages, or components.
-
Develop New Micro Frontends: Develop new micro frontends to replace the identified parts of the old system. These micro frontends are developed and deployed independently, often using modern technologies and practices.
-
Route Requests to New System: Use routing logic to direct requests to the appropriate system – either the new micro frontend or the existing monolithic application. This can be done using a reverse proxy, a frontend router, or similar mechanisms.
-
Incremental Replacement: Gradually replace more components of the old system with new micro frontends. As new features are built or existing features are refactored, they are implemented as separate micro frontends.
-
Retire Old System: Eventually, as more of the system is replaced, the old monolithic frontend becomes redundant. Once all its parts have been replaced and functionality is verified, it can be decommissioned.
The Reverse Strangler Pattern in the context of micro frontends is a variation of the traditional Strangler Pattern, but with a focus on gradually integrating legacy systems into a new, modern architecture, rather than replacing the old system entirely. This approach is particularly useful when certain parts of the legacy system are still valuable, viable, and not feasible to replace in the short term. Instead of strangling the old system, the new system wraps around and integrates with it.
How the Reverse Strangler Pattern Works in Micro Frontends
-
Build a New System Around the Legacy: Start by building a new micro frontend architecture around the existing legacy system. The new system initially acts as a shell or a facade that interfaces with the legacy system.
-
Gradual Integration: Instead of replacing legacy components, gradually integrate them into the new architecture. This might involve wrapping legacy functionality with new interfaces or building bridges that allow the new and old systems to communicate seamlessly.
-
Incremental Enhancement: Enhance and extend the functionality of the legacy system within the new architecture. This might involve adding new features as micro frontends that work in conjunction with the existing system.
-
Refactoring Legacy Components: Over time, selectively refactor parts of the legacy system to better fit into the new architecture, improving them or adapting their interfaces.
-
Full Integration or Eventual Replacement: Eventually, the legacy system becomes fully integrated into the new architecture, either remaining as a functional part of the ecosystem or being replaced piece by piece, as and when it makes sense.
React – loosly coupled
The question is if each of microfrontends in the network should or should not assume they use React.
//Remote MF
export const mount = (element) => {
const App = () => <h1>Hello, React!</h1>;
if (element) {
createRoot(element).render(<App />);
}
};https://slides.com/noinputsignal/microfrontends-day-1
React – loosly coupled
The question is if each of microfrontends in the network should or should not assume they use React.
//Container MF
import { mount } from 'remote/remote';
import React, { useRef, useEffect } from 'react';
export default () => {
const ref = useRef(null);
useEffect(() => {
mount(ref.current);
});
return <div ref={ref} />;
};React – tightly coupled
In this case you can assume that both parts uses React so its fine the import components across multifrontedend network.
MicroFrontends antipatterns
There is no compression algorithm for experience
MicroFrontends antipatterns
Don't mix components and microfrontends
Microfrontend is typically a subdomain of the large application owned by one team.
I allows the team to mentally comprehend the complexity of the subdomain.
Microfrontend should be an independent app in terms of functionality or at least should be deployed and maintained independently.
Tip: You shouldn't have too many microfrontends
MicroFrontends antipatterns
Don't mix too many technologies and frameworks in microfrontends network, unless you really should.
From the perspective of the user we have just one app and it should be consistent.
Reasons to have multiframework approach:
1. legacy code
2. teams with different skills
3. migrations
MicroFrontends antipatterns
Dependency hell
Don't share all the dependencies across the network
MicroFrontends antipatterns
Bidirectional data binding
MicroFrontends antipatterns
Global State
Use EventEmitter instead of global state.
The system should be loosly coupled by highly aligned
MicroFrontends antipatterns
Multiple api calls
Try to prevent multiplying requests across multifrontend network
Microfrontends – Day 2
By noinputsignal
Microfrontends – Day 2
- 15