🎉 First video: Interview with Meta Developer

Microfrontend Architecture

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.

Core Concepts

1

Independent Applications

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.).

2

Isolation

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.

3

Composition

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.

4

Independent Deployment

Each microfrontend can be updated and deployed separately without affecting other parts of the system. This speeds up the development cycle and reduces risks.

Implementation Approaches

Server-Side Composition (SSI)

Microfrontends are assembled on the server side using technologies like Server-Side Includes (SSI), Edge-Side Includes (ESI), or templating engines.

Pros:

  • Simple implementation
  • Good performance (client receives ready page)

Cons:

  • Complex interaction between microfrontends on client side
  • Less flexibility for dynamic updates

Build-Time Integration

Microfrontends are published as npm packages and included in the main application at build time.

Pros:

  • Easy integration
  • Optimized build

Cons:

  • Loss of deployment independence (need to rebuild entire application)
  • Cannot update one microfrontend without rebuilding

Run-Time Integration via iframes

Each microfrontend loads in a separate iframe.

Pros:

  • Complete isolation (styles, JavaScript, global variables)
  • Simple integration

Cons:

  • Performance issues
  • Difficulties with communication and routing
  • Poor accessibility (a11y)

Run-Time Integration via JavaScript (Module Federation)

Most modern approach: microfrontends are loaded dynamically at runtime via Webpack Module Federation or similar tools.

Pros:

  • Independent deployment
  • Shared dependencies (react, react-dom) load once
  • Flexible composition

Cons:

  • Complex setup
  • Requires careful dependency version management

Web Components

Microfrontends are implemented as Custom Elements (Web Components).

Pros:

  • Native browser support
  • Encapsulation via Shadow DOM

Cons:

  • Limited support in older browsers
  • Integration complexity with some frameworks

Module Federation Example Structure

Microfrontend Advantages

  1. Team Independence
    Each team works on their microfrontend autonomously without blocking others. This speeds up development and reduces dependencies.

  2. Technology Diversity
    Different microfrontends can use different frameworks and libraries. For example, one in React, another in Vue, third in Angular.

  3. Independent Deployment
    Updating one microfrontend doesn't require redeploying the entire application. This reduces risks and speeds up new feature delivery.

  4. Development Scalability
    A large project can be divided between multiple teams, each focusing on their business domain.

  5. Simplified Migration
    You can gradually migrate parts of a monolith to new technologies without rewriting the entire application at once.

Disadvantages and Challenges

  1. Infrastructure Complexity
    Need to set up build, CI/CD, monitoring, and logging for each microfrontend.

  2. Increased Application Size
    Without using shared dependencies, the same libraries (React, lodash) may load multiple times.

  3. Communication Complexity
    Microfrontends must somehow exchange data and events. Need a well-thought-out mechanism (e.g., event bus, shared state).

  4. Versioning Issues
    Different microfrontends may use different versions of the same library, leading to conflicts.

  5. Code Duplication
    Common UI components and utilities need to be either extracted into separate packages or duplicated in each microfrontend.

  6. Testing Complexity
    E2E tests must account for interaction of all microfrontends. Integration tests become more complex.

When to Use Microfrontends

Warning:

Microfrontends are not a silver bullet. They add complexity and are not suitable for all projects.

Suitable for:

  • Large applications with multiple teams
  • Projects requiring deployment independence
  • Gradual migration from legacy code to new technologies
  • Complex enterprise systems with different business domains

Not suitable for:

  • Small projects or MVPs
  • Teams of 1-3 developers
  • Projects with simple structure where modular architecture is sufficient

Best Practices

1

Define Boundaries

Divide microfrontends by business domains (products, checkout, user-profile), not by technical layers.

2

Use Shared Dependencies

Configure shared dependencies in Module Federation to avoid library duplication.

3

Create a Design System

Common UI components (buttons, inputs, modals) should be in a separate library used by all microfrontends.

4

Plan Communication

Use an event bus (e.g., based on CustomEvents) or shared state (Redux, Zustand) for data exchange between microfrontends.

5

Monitoring and Logging

Set up centralized monitoring (Sentry, DataDog) to track errors across all microfrontends.

6

Versioning Agreements

Agree on dependency update policy and use semver for compatibility control.

Communication Example Between Microfrontends

// 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]);

Alternatives to Microfrontends

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.

Useful Resources