Micro Frontends: Achieving Full Team Autonomy with Build-Time Integration | by Eden Ella | Feb, 2025
A “subscriber” (Bit) component defines how MFEs in that system should be implemented and provides the API for them to register into the system. The subscriber or container implements the shared context for the MFEs (app theme, app layout, global state, shared actions, etc).
/**
* Subscriber
*/
// …
export class App {
// …
registerRoutes(routes: RouteType[]) {
this.routes.push(…routes);
}
registerHeaderLinks(headerLinks: HeaderLink[]) {
this.headerLinks.push(…headerLinks);
}
listRoutes() {
return this.routes;
}
listHeaderLinks() {
return this.headerLinks;
}
renderApp() {
return (
{this.listRoutes().map((route) => (
key={route.path}
path={route.path}
element={route.element}
/>
))}
);
}
}
A (non-Bit) application defines which MFEs should be part of the application, as well as how the app should be built and deployed.
The app is agnostic to the way MFEs are integrated. It only consumes the MFEs and executes the registration and rendering as the subscriber component defines it.
/**
* Application
*/
import { createRoot } from ‘react-dom/client’;
import { App, Frontend } from ‘@cosmo-flux/foundation.subscriber’;
import { marketingMfe } from ‘@cosmo-flux/marketing.marketing-mfe’;
import { bookingMfe } from ‘@cosmo-flux/booking.booking-mfe’;
const app = new App();
const frontends: Frontend[] = [marketingMfe, bookingMfe];
frontends.forEach((frontend) => frontend(app));
createRoot(document.getElementById(‘root’)!).render(
<>{app.renderApp()}>
);
3. Implementing the MFEs
Use the proper Bit template to generate the boilerplate for your MFE components, or use Bit’s Hope AI to generate the necessary components.
The important thing is to implement the Frontend type as it is defined by the component responsible for the integration of MFES (the subscriber). As you can see, in order to keep the MFE teams as independent as possible, we implement an inversion of control using dependency injection.
An MFE determines how it integrates into the system and which shared functionalities and states it uses (notifications, user info, etc).
For example, the Booking MFE registers the Red Planet Reservations page into the app.
/**
* Booking Micro Frontend
*/
import type { Frontend } from ‘@cosmo-flux/foundation.subscriber’;
import { RedPlanetReservations } from ‘@cosmo-flux/booking.pages.red-planet-reservations’;
export const bookingMfe: Frontend = (app) => {
app.registerRoutes([
{
path: ‘/reservations’,
element: ,
},
]);
app.registerHeaderLinks([
{
label: ‘Reservations’,
path: ‘/reservations’,
},
]);
return app;
};
Since both the MFE and the page are Bit components, they are consumed using their package name (as you can see in the previous example, the application shell project uses the booking MFE as a regular package).
4. Create a Webhook to Trigger the App Shell CI/CD When an MFE Version is Released
To replicate the seamless developer experience and smooth collaboration seen in runtime MFE setups, we can leverage Bit Platform to configure a webhook that automatically triggers the shell application’s build and (possibly) deployment whenever a new stable MFE version is released.
This approach empowers MFE teams to deploy their applications independently without relying on other teams.
While automating the build and deployment process may seem risky at first, when implemented correctly, it is arguably safer than the conventional runtime integrations we commonly accept.
Build-time integration enables comprehensive end-to-end testing and automated verifications, ensuring that failures occur during the build process rather than at runtime.