Loading...
Loading...
Microfrontend is an architectural approach where a frontend application is split into multiple independent applications (micro-apps), each responsible for its own part of functionality. These applications can be developed, tested, and deployed independently of each other, but ultimately are combined into a single user interface.
Key Idea:
Microfrontend is the application of microservices architecture principles to frontend development. Each team can work on their part of the application autonomously, using their own tech stack and releasing updates independently.
Each microfrontend is a standalone application with its own business logic, styles, and dependencies. It can be written in any framework (React, Vue, Angular, etc.).
Microfrontends are isolated from each other: changes in one should not break the work of others. This is achieved through style encapsulation mechanisms, using Shadow DOM or CSS Modules.
There is a shell application (host, container) that combines all microfrontends into a single interface. It manages routing and determines which microfrontend to show on a specific route.
Each microfrontend can be updated and deployed separately without affecting other parts of the system. This speeds up the development cycle and reduces risks.
Microfrontends are assembled on the server side using technologies like Server-Side Includes (SSI), Edge-Side Includes (ESI), or templating engines.
Pros:
Cons:
Microfrontends are published as npm packages and included in the main application at build time.
Pros:
Cons:
Each microfrontend loads in a separate iframe.
Pros:
Cons:
Most modern approach: microfrontends are loaded dynamically at runtime via Webpack Module Federation or similar tools.
Pros:
Cons:
Microfrontends are implemented as Custom Elements (Web Components).
Pros:
Cons:
Team Independence
Each team works on their microfrontend autonomously without blocking others. This speeds up development and reduces dependencies.
Technology Diversity
Different microfrontends can use different frameworks and libraries. For example, one in React, another in Vue, third in Angular.
Independent Deployment
Updating one microfrontend doesn't require redeploying the entire application. This reduces risks and speeds up new feature delivery.
Development Scalability
A large project can be divided between multiple teams, each focusing on their business domain.
Simplified Migration
You can gradually migrate parts of a monolith to new technologies without rewriting the entire application at once.
Infrastructure Complexity
Need to set up build, CI/CD, monitoring, and logging for each microfrontend.
Increased Application Size
Without using shared dependencies, the same libraries (React, lodash) may load multiple times.
Communication Complexity
Microfrontends must somehow exchange data and events. Need a well-thought-out mechanism (e.g., event bus, shared state).
Versioning Issues
Different microfrontends may use different versions of the same library, leading to conflicts.
Code Duplication
Common UI components and utilities need to be either extracted into separate packages or duplicated in each microfrontend.
Testing Complexity
E2E tests must account for interaction of all microfrontends. Integration tests become more complex.
Warning:
Microfrontends are not a silver bullet. They add complexity and are not suitable for all projects.
Suitable for:
Not suitable for:
Divide microfrontends by business domains (products, checkout, user-profile), not by technical layers.
Configure shared dependencies in Module Federation to avoid library duplication.
Common UI components (buttons, inputs, modals) should be in a separate library used by all microfrontends.
Use an event bus (e.g., based on CustomEvents) or shared state (Redux, Zustand) for data exchange between microfrontends.
Set up centralized monitoring (Sentry, DataDog) to track errors across all microfrontends.
Agree on dependency update policy and use semver for compatibility control.
// Shared Event Bus (shared library)
class EventBus {
private events: Map<string, Function[]> = new Map();
emit(event: string, data?: any) {
const handlers = this.events.get(event) || [];
handlers.forEach(handler => handler(data));
}
on(event: string, handler: Function) {
const handlers = this.events.get(event) || [];
handlers.push(handler);
this.events.set(event, handlers);
}
off(event: string, handler: Function) {
const handlers = this.events.get(event) || [];
const index = handlers.indexOf(handler);
if (index > -1) {
handlers.splice(index, 1);
}
this.events.set(event, handlers);
}
}
export const eventBus = new EventBus();
// In Products microfrontend
import { eventBus } from '@shared/event-bus';
function addToCart(product: Product) {
eventBus.emit('cart:add', product);
}
// In Cart microfrontend
import { eventBus } from '@shared/event-bus';
useEffect(() => {
const handler = (product: Product) => {
setCartItems([...cartItems, product]);
};
eventBus.on('cart:add', handler);
return () => {
eventBus.off('cart:add', handler);
};
}, [cartItems]);
Before implementing microfrontends, consider simpler approaches:
Summary:
Microfrontends are a powerful tool for scaling large teams and complex applications. They allow teams to work independently and deploy changes autonomously. However, they add significant complexity to infrastructure, testing, and development. Use microfrontends only when their benefits (team independence, technology diversity) outweigh the costs of implementation and maintenance.