My journey into the world of microfrontends began within the Guilds: spaces dedicated to sharing and experimentation where people can exchange skills, discuss new architectural solutions, and explore emerging technologies.
It was during the i3Skillz Guild, held in the third quarter of 2024, that I was first introduced to the microfrontend paradigm. Together with my Guild colleagues, i analyzed its advantages, challenges, and potential applications in real-world projects.
Shortly after the i3Skillz Guild ended, I started working on a project that gave me the opportunity to apply microfrontends in practice. This experience allowed me to fully grasp their true potential and understand how to use them effectively within a project’s architecture.
In this article, I will share my perspective on microfrontends: what they are, when and how to use them (Module Federation and Native Federation) with their respective pros and cons, and finally a small practical example of an architecture with two Angular microfrontends.
Microfrontends: what are they?
A microfrontend is an architectural style for developing web applications applied specifically to the frontend layer. It follows the same principle used for backend microservices: instead of a single large monolithic application, the user interface is divided into multiple parts, each independent and with a clearly defined purpose.
Each microfrontend is therefore a standalone application that “exposes” its own functionality externally and can:
- be developed and deployed through its own pipeline;
- use different technologies and frameworks from the other microfrontends (if supported by the chosen stack);
- evolve independently without blocking the rest of the system.
Microfrontends: when to use them?
Microfrontends are not always the ideal solution for every project. In many cases, they are adopted simply because they’re “trending,” yet the architectural (and therefore maintenance) complexity often doesn’t justify the development effort required to implement them.
Microfrontends can be useful when:
- the platform is large and constantly evolving;
- multiple teams work on the same frontend in isolated, independent areas;
- independent and frequent releases are needed;
- you want to experiment with different technologies or libraries without rewriting the entire codebase;
- there are distinct areas of the application that can truly operate independently.
On the other hand, I wouldn’t recommend using microfrontends when:
- the project is small or has a limited development team;
- there is no need for independent releases;
- the application has many components with tightly connected logic.
A typical use case
A practical example that helps illustrate when microfrontends can be particularly useful is the development of an e-commerce portal composed of several sections:
- a product catalog, listing all items divided by category;
- a shopping cart, showing a summary of the products selected for purchase;
- the checkout process, with its integrations for payment methods.
In this case, it makes sense to divide the application into separate, isolated modules — which is exactly where the use of microfrontends can be beneficial.
How to use Microfrontends: Module Federation and Native Federation
On the technical side, there are several strategies for running multiple microfrontends in the same application.
In order to implement these technologies, you need an application that acts as a “conductor” for the microfrontends, deciding which microfrontend should be executed at a given point in the application.
But how are microfrontends implemented in practice? There are several strategies to make multiple microfrontends coexist within the same application. The two most commonly used today are Module Federation and Native Federation.
Module Federation
This is currently the most widely adopted solution, based on webpack. The goal is to expose application modules externally so that they can be integrated into the application “shell.”
Advantages
- Extremely flexible.
- Supports library sharing across different frameworks (React, Angular, etc.).
- Enables independent deployment of microfrontends.
Disadvantages
- Module Federation is Webpack-based, so specific integration is required if you want to use other technologies such as Vite, esbuild, or other bundlers.
- For large-scale projects, configuration can become complex.
Native Federation
It’s an evolution of the Module Federation concept, without being tied to webpack. Native Federation is based on browser standards, specifically ES modules (ECMAScript) and dynamic imports.
Advantages
- No dependency on specific bundlers.
- “Future-proof” — Native Federation directly leverages browser primitives without relying on webpack.
- Ideal for hybrid architectures and highly independent microfrontends.
Disadvantages
- A less mature ecosystem compared to webpack.
- Limited documentation and community support.
- Some optimizations (such as automatic dependency sharing) require additional effort.
Implementing a simple microfrontend architecture
Let me show you how easy it is to set up a microfrontend architecture.
Building on the work done in the i3Skillz Guild, I chose to use Angular to create two microfrontends that will be rendered within the same page. The shell will therefore have a page where two microfrontends will be rendered simultaneously.
Managing the app.component.ts file
By default, Angular starts its execution with the app.component.ts file, within which I have integrated all the logic I need to display the two microfrontends.
import {Component, OnInit, signal} from '@angular/core';
import {RouterOutlet} from '@angular/router';
import {loadRemoteModule} from '@angular-architects/native-federation';
import {NgComponentOutlet} from '@angular/common';
@Component({
selector: 'app-root',
imports: [RouterOutlet, NgComponentOutlet],
template: `
<main class="main">
<h1>Shell Application</h1>
<div style="display: flex; gap: 20px; margin-top: 20px;">
@if (mfe1Component()) {
<div style="flex: 1;">
<ng-container *ngComponentOutlet="mfe1Component()"></ng-container>
</div>
}
@if (mfe2Component()) {
<div style="flex: 1;">
<ng-container *ngComponentOutlet="mfe2Component()"></ng-container>
</div>
}
</div>
<router-outlet></router-outlet>
</main>`,
})
export class AppComponent implements OnInit {
mfe1Component = signal<any | undefined>(undefined);
mfe2Component = signal<any | undefined>(undefined);
protected readonly title = signal('shell-native-federation');
ngOnInit() {
loadRemoteModule('mfe1', './Component').then(m1 => {
this.mfe1Component.set(m1.Mfe1Component);
});
loadRemoteModule('mfe2', './Component').then(m2 => {
this.mfe2Component.set(m2.Mfe2Component);
});
}
}
I used the loadRemoteModule function provided by the @angular-architects/native-federation library – a library that integrates Native Federation for Angular – which imports the module called Component from the two remote microfrontends mfe1 and mfe2.
I used the same approach to export the two components mentioned above, so I will show you the one used for mfe1 component.
Microfrontend mfe1
To export components, add the @angular-architects/native-federation library to the application and, in the federation configuration file federation.config.js, export the component in question.
const { withNativeFederation, shareAll } = require('@angular-architects/native-federation/config');
module.exports = withNativeFederation({
name: 'mfe-1-native-federation',
exposes: {
'./Component': './src/app/components/mfe1/mfe1.component.ts',
},
shared: {
...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }),
},
skip: [
'rxjs/ajax',
'rxjs/fetch',
'rxjs/testing',
'rxjs/webSocket',
],
features: {
ignoreUnusedDeps: true
}
});
The following code manages the export the image with the name ./Component from the mfe1 component of the application.
import {Component} from '@angular/core';
@Component({
selector: 'app-mfe1',
imports: [],
template: `
<div class="mfe-container">
<h2>MFE 1</h2>
<p>This is the MFE 1 component.</p>
</div>
`,
styles: `
.mfe-container {
border: 5px solid red;
padding: 20px;
background-color: #ffe6e6;
}
`,
})
export class Mfe1Component {
}
The mfe1 component is very simple, rendering a simple div with text in its HTML to distinguish between the two components. The export logic is therefore only within the manifest file discussed previously.
The inifFederation method and the manifest file
After exporting the two microfrontends, it is a good idea to check that the applications are running on different ports: I therefore set mfe1 to port 4201 and mfe2 to port 4202.
Once this is done, the shell’s main.ts file needs to be modified to ensure that the federation runs correctly.
import { initFederation } from '@angular-architects/native-federation';
initFederation('federation.manifest.json')
.catch(err => console.error(err))
.then(_ => import('./bootstrap'))
.catch(err => console.error(err));
Using the initFederation function, which takes the manifest file as input, I communicate which ports the microfrontends will use.
To manage any changes in the execution environment, I have set up a .json file, which is easy to modify.
The manifest file will be very simple, containing the name-location pair of our microfrontend.
{
"mfe1": "http://localhost:4201/remoteEntry.json",
"mfe2": "http://localhost:4202/remoteEntry.json"
}
The application and the two microfrontends running
Everything is ready, we just need to start the three applications.
Let’s go to the shell address – in my case http://localhost:4200 – to see the shell with the two microfrontends.

Conclusions
The world of microfrontends is not just a technical choice but also an organizational one. This approach is preferable to a traditional monolithic architecture when it’s clear that the application to be developed consists of distinct, self-contained modules with few shared logics. In this way, development teams can be organized to work simultaneously on different components, effectively parallelizing the workflow.
However, if the application to be developed relies on a large number of interconnected logics, a monolithic architecture would be the better option — avoiding unnecessary complexity and preserving a more coherent structure than a microfrontend-based solution.