DEV Community

Maksym
Maksym

Posted on

Microfrontends: Guide to Modern Frontend Architecture

Front-end Wizard
As web applications grow in complexity and teams scale, traditional monolithic frontend architectures often become bottlenecks. Enter microfrontends - an architectural pattern that extends the microservices philosophy to the frontend, enabling teams to develop, deploy, and maintain user interfaces independently while creating cohesive user experiences.

What Are Microfrontends?

Microfrontends represent an architectural approach where a frontend application is decomposed into smaller, independent applications that work together to form a cohesive user experience. Each microfrontend is owned by a different team and can be developed, tested, and deployed independently.

Think of it as having multiple mini-applications that combine to create one larger application, similar to how microservices work on the backend.

⚠️ Important Disclaimer: This Is Not for Small Projects

Microfrontends are primarily beneficial for large-scale enterprise applications with multiple development teams. This architectural pattern introduces significant complexity and overhead that is rarely justified for small to medium-sized projects.

Consider microfrontends only if you have:

  • Multiple development teams (5+ teams) working on the same application
  • Large codebases that are difficult to manage as a monolith
  • Need for independent deployment cycles across different business domains
  • Different teams that prefer different technology stacks
  • Organizational structure that supports autonomous team ownership

For smaller projects, a well-structured monolithic frontend will be:

  • Faster to develop and deploy
  • Easier to maintain and debug
  • More cost-effective
  • Less complex to test and monitor
  • Better performing out of the box

The complexity introduced by microfrontends - including build tooling, deployment orchestration, inter-application communication, and monitoring - often outweighs the benefits for teams with fewer than 20-30 frontend developers or applications serving fewer than millions of users.

Core Principles

Technology Agnostic: Each microfrontend can use different frameworks, libraries, and technologies
Independent Deployment: Teams can deploy their parts without coordinating with others
Team Autonomy: Each team owns their domain from UI to backend services
Isolation: Failures in one microfrontend shouldn't cascade to others
Native Integration: Despite being separate, they should feel like one cohesive application

Why Microfrontends?

Traditional Monolithic Frontend Challenges

Before diving into microfrontends, let's understand the problems they solve:

Scalability Issues: As teams grow, working on a single codebase becomes increasingly difficult
Technology Lock-in: The entire application is tied to one technology stack
Deployment Bottlenecks: Any change requires rebuilding and deploying the entire application
Team Dependencies: Teams must coordinate releases and changes
Code Conflicts: Multiple teams working on the same codebase leads to merge conflicts

Benefits of Microfrontends

Independent Development: Teams can work autonomously on their domains
Technology Diversity: Use the best tool for each specific job
Faster Deployments: Deploy individual parts without affecting the whole system
Better Scalability: Scale development teams without coordination overhead
Fault Isolation: Issues in one area don't bring down the entire application
Legacy Migration: Gradually migrate from legacy systems without big-bang rewrites

Potential Drawbacks

Increased Complexity: More moving parts means more complexity
Performance Overhead: Multiple bundles and potential duplication
Consistency Challenges: Maintaining UI/UX consistency across teams
Operational Overhead: More deployment pipelines and monitoring
Initial Setup Cost: Higher upfront investment in tooling and processes

Microfrontend Architecture Patterns

1. Build-Time Integration

Microfrontends are integrated during the build process, creating a single deployable artifact.

// Package.json dependencies
{
  "dependencies": {
    "@company/header-microfrontend": "^1.2.0",
    "@company/sidebar-microfrontend": "^2.1.0",
    "@company/main-content-microfrontend": "^1.5.0"
  }
}

// App.js
import Header from '@company/header-microfrontend';
import Sidebar from '@company/sidebar-microfrontend';
import MainContent from '@company/main-content-microfrontend';

function App() {
  return (
    <div>
      <Header />
      <div className="content">
        <Sidebar />
        <MainContent />
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Pros: Simple deployment, good performance, type safety
Cons: Tight coupling, coordinated releases, technology lock-in

2. Run-Time Integration via JavaScript

Microfrontends are loaded and integrated at runtime in the browser.

// Container application
class MicrofrontendLoader {
  async loadMicrofrontend(name, host) {
    const script = document.createElement('script');
    script.src = `${host}/remoteEntry.js`;
    script.onload = () => {
      window[name].init({
        host: this.host
      });
    };
    document.head.appendChild(script);
  }

  async mountMicrofrontend(name, element) {
    await window[name].mount(element);
  }
}

// Usage
const loader = new MicrofrontendLoader();
await loader.loadMicrofrontend('headerApp', 'http://header.example.com');
await loader.mountMicrofrontend('headerApp', document.getElementById('header'));
Enter fullscreen mode Exit fullscreen mode

Pros: True independence, different technologies, independent deployments
Cons: More complex, potential performance issues, runtime errors

3. Web Components

Using native web components or frameworks that compile to web components.

// Microfrontend as Web Component
class HeaderMicrofrontend extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `
      <header class="app-header">
        <nav>
          <a href="/">Home</a>
          <a href="/profile">Profile</a>
          <a href="/settings">Settings</a>
        </nav>
      </header>
    `;
  }
}

customElements.define('app-header', HeaderMicrofrontend);

// Usage in container
<html>
  <body>
    <app-header></app-header>
    <main id="main-content"></main>
    <app-footer></app-footer>

    <script src="header-microfrontend.js"></script>
    <script src="footer-microfrontend.js"></script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Pros: Framework agnostic, good encapsulation, standard technology
Cons: Limited browser support (improving), styling challenges

4. Server-Side Composition

Microfrontends are composed on the server before being sent to the browser.

// Express.js server composition
app.get('/', async (req, res) => {
  const [header, content, footer] = await Promise.all([
    fetch('http://header-service/render'),
    fetch('http://content-service/render'),
    fetch('http://footer-service/render')
  ]);

  const page = `
    <!DOCTYPE html>
    <html>
      <head>
        <title>My App</title>
      </head>
      <body>
        ${await header.text()}
        ${await content.text()}
        ${await footer.text()}
      </body>
    </html>
  `;

  res.send(page);
});
Enter fullscreen mode Exit fullscreen mode

Pros: Better SEO, faster initial load, server-side optimization
Cons: Server complexity, less dynamic, caching challenges

5. Edge-Side Includes (ESI)

Using CDN or edge servers to compose microfrontends.

<!-- ESI Template -->
<html>
  <head>
    <title>My Application</title>
  </head>
  <body>
    <esi:include src="http://header.example.com/header" />
    <esi:include src="http://content.example.com/main" />
    <esi:include src="http://footer.example.com/footer" />
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Pros: Great performance, edge caching, independent scaling
Cons: CDN dependency, limited dynamic behavior, ESI support required

Popular Microfrontend Frameworks and Tools

Module Federation (Webpack 5)

Module Federation allows sharing code between different applications at runtime.

// webpack.config.js for microfrontend
const ModuleFederationPlugin = require('@module-federation/webpack');

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'header',
      filename: 'remoteEntry.js',
      exposes: {
        './Header': './src/Header',
      },
      shared: {
        react: { singleton: true },
        'react-dom': { singleton: true }
      }
    })
  ]
};

// webpack.config.js for container
const ModuleFederationPlugin = require('@module-federation/webpack');

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'container',
      remotes: {
        header: 'header@http://localhost:3001/remoteEntry.js'
      },
      shared: {
        react: { singleton: true },
        'react-dom': { singleton: true }
      }
    })
  ]
};

// Using the remote component
import React, { Suspense } from 'react';
const RemoteHeader = React.lazy(() => import('header/Header'));

function App() {
  return (
    <div>
      <Suspense fallback={<div>Loading Header...</div>}>
        <RemoteHeader />
      </Suspense>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Single-SPA

A framework for building microfrontend applications with multiple frameworks.

// Root application
import { registerApplication, start } from 'single-spa';

registerApplication({
  name: 'navbar',
  app: () => import('./navbar/navbar.app.js'),
  activeWhen: () => true
});

registerApplication({
  name: 'dashboard',
  app: () => import('./dashboard/dashboard.app.js'),
  activeWhen: location => location.pathname.startsWith('/dashboard')
});

start();

// Microfrontend application
import React from 'react';
import ReactDOM from 'react-dom';
import singleSpaReact from 'single-spa-react';
import Dashboard from './Dashboard';

const lifecycles = singleSpaReact({
  React,
  ReactDOM,
  rootComponent: Dashboard,
  errorBoundary(err, info, props) {
    return <div>Error in Dashboard: {err.message}</div>;
  }
});

export const { bootstrap, mount, unmount } = lifecycles;
Enter fullscreen mode Exit fullscreen mode

Bit

A tool for component-driven development and sharing.

# Initialize Bit workspace
bit init

# Create and export components
bit add src/components/Header
bit tag Header --ver 1.0.0
bit export user.collection/header

# Import in other projects
bit import user.collection/header
Enter fullscreen mode Exit fullscreen mode

Qiankun

An Alibaba-developed microfrontend framework.

// Main application
import { registerMicroApps, start } from 'qiankun';

registerMicroApps([
  {
    name: 'react-app',
    entry: '//localhost:3000',
    container: '#react-container',
    activeRule: '/react'
  },
  {
    name: 'vue-app',
    entry: '//localhost:8080',
    container: '#vue-container',
    activeRule: '/vue'
  }
]);

start();

// Microfrontend application
// public-path.js
if (window.__POWERED_BY_QIANKUN__) {
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

// index.js
import './public-path';
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

function render(props) {
  const { container } = props;
  ReactDOM.render(
    <App />, 
    container ? container.querySelector('#root') : document.querySelector('#root')
  );
}

if (!window.__POWERED_BY_QIANKUN__) {
  render({});
}

export async function bootstrap() {
  console.log('[react16] react app bootstraped');
}

export async function mount(props) {
  console.log('[react16] props from main framework', props);
  render(props);
}

export async function unmount(props) {
  const { container } = props;
  ReactDOM.unmountComponentAtNode(
    container ? container.querySelector('#root') : document.querySelector('#root')
  );
}
Enter fullscreen mode Exit fullscreen mode

Implementation Best Practices

1. Define Clear Boundaries

// Domain-based boundaries
const microfrontends = {
  'user-management': {
    routes: ['/users', '/profile', '/settings'],
    team: 'user-team',
    repository: 'user-management-mfe'
  },
  'product-catalog': {
    routes: ['/products', '/categories'],
    team: 'product-team',
    repository: 'product-catalog-mfe'
  },
  'order-management': {
    routes: ['/orders', '/checkout', '/payment'],
    team: 'order-team',
    repository: 'order-management-mfe'
  }
};
Enter fullscreen mode Exit fullscreen mode

2. Establish Communication Patterns

// Event-driven communication
class MicrofrontendEventBus {
  constructor() {
    this.events = {};
  }

  subscribe(event, callback) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(callback);
  }

  publish(event, data) {
    if (this.events[event]) {
      this.events[event].forEach(callback => callback(data));
    }
  }

  unsubscribe(event, callback) {
    if (this.events[event]) {
      this.events[event] = this.events[event].filter(cb => cb !== callback);
    }
  }
}

// Global event bus
window.microfrontendEventBus = new MicrofrontendEventBus();

// Usage in microfrontend
window.microfrontendEventBus.subscribe('user-logged-in', (userData) => {
  updateUserInterface(userData);
});

window.microfrontendEventBus.publish('user-logged-in', { 
  id: 123, 
  name: 'John Doe' 
});
Enter fullscreen mode Exit fullscreen mode

3. Shared State Management

// Shared state store
class SharedStateStore {
  constructor() {
    this.state = {};
    this.subscribers = [];
  }

  getState() {
    return { ...this.state };
  }

  setState(newState) {
    this.state = { ...this.state, ...newState };
    this.notifySubscribers();
  }

  subscribe(callback) {
    this.subscribers.push(callback);
    return () => {
      this.subscribers = this.subscribers.filter(sub => sub !== callback);
    };
  }

  notifySubscribers() {
    this.subscribers.forEach(callback => callback(this.state));
  }
}

// Global shared state
window.sharedState = new SharedStateStore();

// Usage
const unsubscribe = window.sharedState.subscribe((state) => {
  console.log('State updated:', state);
});

window.sharedState.setState({ user: { id: 1, name: 'John' } });
Enter fullscreen mode Exit fullscreen mode

4. Design System Integration

// Design system package
// design-system/index.js
export const theme = {
  colors: {
    primary: '#007bff',
    secondary: '#6c757d',
    success: '#28a745'
  },
  typography: {
    fontFamily: 'Inter, sans-serif',
    sizes: {
      small: '14px',
      medium: '16px',
      large: '20px'
    }
  }
};

export const Button = ({ variant, children, ...props }) => {
  const styles = {
    backgroundColor: theme.colors[variant] || theme.colors.primary,
    color: 'white',
    border: 'none',
    padding: '8px 16px',
    borderRadius: '4px',
    fontFamily: theme.typography.fontFamily
  };

  return <button style={styles} {...props}>{children}</button>;
};

// Usage in microfrontends
import { Button, theme } from '@company/design-system';

function UserProfile() {
  return (
    <div>
      <h1 style={{ color: theme.colors.primary }}>User Profile</h1>
      <Button variant="primary">Save Changes</Button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

5. Error Handling and Fallbacks

// Error boundary for microfrontends
class MicrofrontendErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  componentDidCatch(error, errorInfo) {
    console.error('Microfrontend error:', error, errorInfo);

    // Send error to monitoring service
    this.props.onError?.(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback || (
        <div className="error-fallback">
          <h2>Something went wrong</h2>
          <p>This section is temporarily unavailable.</p>
          <button onClick={() => window.location.reload()}>
            Refresh Page
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

// Usage
function App() {
  return (
    <div>
      <MicrofrontendErrorBoundary 
        fallback={<div>Header unavailable</div>}
        onError={(error) => sendToMonitoring(error)}
      >
        <HeaderMicrofrontend />
      </MicrofrontendErrorBoundary>

      <MicrofrontendErrorBoundary>
        <MainContentMicrofrontend />
      </MicrofrontendErrorBoundary>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Testing Strategies

Unit Testing

// Testing individual microfrontend components
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { UserProfile } from './UserProfile';

describe('UserProfile', () => {
  test('displays user information', () => {
    const user = { id: 1, name: 'John Doe', email: 'john@example.com' };
    render(<UserProfile user={user} />);

    expect(screen.getByText('John Doe')).toBeInTheDocument();
    expect(screen.getByText('john@example.com')).toBeInTheDocument();
  });

  test('handles save button click', async () => {
    const mockSave = jest.fn();
    const user = { id: 1, name: 'John Doe', email: 'john@example.com' };

    render(<UserProfile user={user} onSave={mockSave} />);

    await userEvent.click(screen.getByText('Save Changes'));
    expect(mockSave).toHaveBeenCalledWith(user);
  });
});
Enter fullscreen mode Exit fullscreen mode

Integration Testing

// Testing microfrontend integration
import { render, screen } from '@testing-library/react';
import { MicrofrontendContainer } from './MicrofrontendContainer';

describe('Microfrontend Integration', () => {
  test('loads and displays microfrontends', async () => {
    render(<MicrofrontendContainer />);

    // Wait for microfrontends to load
    await screen.findByTestId('header-microfrontend');
    await screen.findByTestId('main-content-microfrontend');

    expect(screen.getByTestId('header-microfrontend')).toBeInTheDocument();
    expect(screen.getByTestId('main-content-microfrontend')).toBeInTheDocument();
  });

  test('handles microfrontend communication', async () => {
    render(<MicrofrontendContainer />);

    // Simulate user action in one microfrontend
    const loginButton = await screen.findByText('Login');
    await userEvent.click(loginButton);

    // Verify other microfrontends respond
    expect(await screen.findByText('Welcome, John!')).toBeInTheDocument();
  });
});
Enter fullscreen mode Exit fullscreen mode

End-to-End Testing

// Cypress E2E tests
describe('Microfrontend Application', () => {
  it('navigates between microfrontends', () => {
    cy.visit('/');

    // Test navigation
    cy.get('[data-testid="nav-products"]').click();
    cy.url().should('include', '/products');
    cy.get('[data-testid="product-list"]').should('be.visible');

    // Test cross-microfrontend interaction
    cy.get('[data-testid="add-to-cart"]').first().click();
    cy.get('[data-testid="cart-count"]').should('contain', '1');
  });

  it('handles microfrontend failures gracefully', () => {
    // Mock microfrontend failure
    cy.intercept('GET', '**/product-microfrontend.js', { statusCode: 500 });

    cy.visit('/products');
    cy.get('[data-testid="error-fallback"]').should('be.visible');
    cy.get('[data-testid="error-fallback"]').should('contain', 'temporarily unavailable');
  });
});
Enter fullscreen mode Exit fullscreen mode

Performance Optimization

Bundle Optimization

// Webpack configuration for shared dependencies
const ModuleFederationPlugin = require('@module-federation/webpack');

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'shell',
      remotes: {
        header: 'header@http://localhost:3001/remoteEntry.js',
        footer: 'footer@http://localhost:3002/remoteEntry.js'
      },
      shared: {
        react: { 
          singleton: true, 
          eager: true,
          requiredVersion: '^17.0.0'
        },
        'react-dom': { 
          singleton: true, 
          eager: true,
          requiredVersion: '^17.0.0'
        },
        '@company/design-system': {
          singleton: true,
          eager: true
        }
      }
    })
  ]
};
Enter fullscreen mode Exit fullscreen mode

Lazy Loading

// Lazy load microfrontends
import React, { Suspense, lazy } from 'react';

const HeaderMicrofrontend = lazy(() => import('header/Header'));
const ProductsMicrofrontend = lazy(() => import('products/ProductList'));

function App() {
  return (
    <div>
      <Suspense fallback={<div>Loading header...</div>}>
        <HeaderMicrofrontend />
      </Suspense>

      <Route path="/products">
        <Suspense fallback={<div>Loading products...</div>}>
          <ProductsMicrofrontend />
        </Suspense>
      </Route>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Caching Strategies

// Service worker for microfrontend caching
self.addEventListener('fetch', (event) => {
  if (event.request.url.includes('/remoteEntry.js')) {
    event.respondWith(
      caches.open('microfrontend-cache').then((cache) => {
        return cache.match(event.request).then((response) => {
          if (response) {
            // Serve from cache and update in background
            fetch(event.request).then((networkResponse) => {
              cache.put(event.request, networkResponse.clone());
            });
            return response;
          }

          // Fetch and cache
          return fetch(event.request).then((networkResponse) => {
            cache.put(event.request, networkResponse.clone());
            return networkResponse;
          });
        });
      })
    );
  }
});
Enter fullscreen mode Exit fullscreen mode

Deployment and DevOps

CI/CD Pipeline

# GitHub Actions for microfrontend deployment
name: Deploy Microfrontend

on:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Setup Node.js
        uses: actions/setup-node@v2
        with:
          node-version: '16'
      - name: Install dependencies
        run: npm ci
      - name: Run tests
        run: npm test
      - name: Run E2E tests
        run: npm run test:e2e

  build:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Setup Node.js
        uses: actions/setup-node@v2
        with:
          node-version: '16'
      - name: Install dependencies
        run: npm ci
      - name: Build
        run: npm run build
      - name: Upload artifacts
        uses: actions/upload-artifact@v2
        with:
          name: build-files
          path: dist/

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Download artifacts
        uses: actions/download-artifact@v2
        with:
          name: build-files
      - name: Deploy to CDN
        run: |
          aws s3 sync . s3://microfrontend-bucket/header/ --delete
          aws cloudfront create-invalidation --distribution-id ${{ secrets.CLOUDFRONT_ID }} --paths "/header/*"
Enter fullscreen mode Exit fullscreen mode

Container Orchestration

# Kubernetes deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: header-microfrontend
spec:
  replicas: 3
  selector:
    matchLabels:
      app: header-microfrontend
  template:
    metadata:
      labels:
        app: header-microfrontend
    spec:
      containers:
      - name: header-microfrontend
        image: company/header-microfrontend:latest
        ports:
        - containerPort: 3000
        env:
        - name: API_URL
          value: "https://api.company.com"
---
apiVersion: v1
kind: Service
metadata:
  name: header-microfrontend-service
spec:
  selector:
    app: header-microfrontend
  ports:
  - port: 80
    targetPort: 3000
Enter fullscreen mode Exit fullscreen mode

Real-World Use Cases

E-commerce Platform

┌─────────────────────────────────────────────────────────┐
│                    Shell Application                    │
├─────────────────────────────────────────────────────────┤
│  Header MFE  │  Navigation MFE  │  Search MFE           │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  Product Catalog MFE     │    Shopping Cart MFE         │
│  - Product Listing       │    - Cart Management         │
│  - Product Details       │    - Checkout Process        │
│  - Categories            │    - Payment Integration     │
│                                                         │
├─────────────────────────────────────────────────────────┤
│  User Account MFE        │    Order Management MFE      │
│  - Profile Management    │    - Order History           │
│  - Authentication        │    - Order Tracking          │
│  - Preferences           │    - Returns/Refunds         │
├─────────────────────────────────────────────────────────┤
│                    Footer MFE                           │
└─────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Enterprise Dashboard

┌─────────────────────────────────────────────────────────┐
│  Global Navigation & Shell                              │
├─────────────────────────────────────────────────────────┤
│  Analytics MFE           │    User Management MFE       │
│  - Dashboards            │    - User Accounts           │
│  - Reports               │    - Permissions             │
│  - Data Visualization    │    - Role Management         │
├─────────────────────────────────────────────────────────┤
│  Content Management MFE  │    System Settings MFE       │
│  - CMS Interface         │    - Configuration           │
│  - Media Library         │    - Integrations            │
│  - Publishing Tools      │    - API Management          │
└─────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Migration Strategies

Strangler Fig Pattern

// Gradual migration approach
class MigrationRouter {
  constructor() {
    this.routes = new Map();
    this.legacyFallback = '/legacy-app';
  }

  addMicrofrontendRoute(path, microfrontend) {
    this.routes.set(path, microfrontend);
  }

  route(path) {
    for (let [routePath, microfrontend] of this.routes) {
      if (path.startsWith(routePath)) {
        return microfrontend;
      }
    }
    return this.legacyFallback;
  }
}

// Configure migration
const router = new MigrationRouter();
router.addMicrofrontendRoute('/users', 'user-management-mfe');
router.addMicrofrontendRoute('/products', 'product-catalog-mfe');
// Other routes fall back to legacy application
Enter fullscreen mode Exit fullscreen mode

Monitoring and Observability

Performance Monitoring

// Performance monitoring for microfrontends
class MicrofrontendMonitor {
  constructor() {
    this.metrics = [];
  }

  trackLoadTime(microfrontend, loadTime) {
    this.metrics.push({
      type: 'load_time',
      microfrontend,
      value: loadTime,
      timestamp: Date.now()
    });

    // Send to monitoring service
    this.sendMetrics();
  }

  trackError(microfrontend, error) {
    this.metrics.push({
      type: 'error',
      microfrontend,
      error: error.message,
      stack: error.stack,
      timestamp: Date.now()
    });

    this.sendMetrics();
  }

  sendMetrics() {
    if (this.metrics.length > 0) {
      fetch('/api/metrics', {
        method: 'POST',
        body: JSON.stringify(this.metrics),
        headers: { 'Content-Type': 'application/json' }
      });
      this.metrics = [];
    }
  }
}

// Usage
const monitor = new MicrofrontendMonitor();

// Track microfrontend load time
const startTime = performance.now();
loadMicrofrontend('header').then(() => {
  const loadTime = performance.now() - startTime;
  monitor.trackLoadTime('header', loadTime);
});
Enter fullscreen mode Exit fullscreen mode

Error Tracking

// Centralized error tracking
window.addEventListener('error', (event) => {
  const microfrontend = identifyMicrofrontend(event.filename);
  monitor.trackError(microfrontend, event.error);
});

window.addEventListener('unhandledrejection', (event) => {
  const microfrontend = identifyMicrofrontend(event.reason.stack);
  monitor.trackError(microfrontend, event.reason);
});

function identifyMicrofrontend(source) {
  if (source.includes('header')) return 'header-mfe';
  if (source.includes('products')) return 'products-mfe';
  return 'unknown';
}
Enter fullscreen mode Exit fullscreen mode

Security Considerations

Content Security Policy

<!-- CSP for microfrontend security -->
<meta http-equiv="Content-Security-Policy" 
      content="script-src 'self' 
               https://header.company.com 
               https://products.company.com 
               https://orders.company.com;
               style-src 'self' 'unsafe-inline';
               img-src 'self' https://cdn.company.com;">
Enter fullscreen mode Exit fullscreen mode

Cross-Origin Communication

// Secure postMessage communication
class SecureCommunication {
  constructor(allowedOrigins) {
    this.allowedOrigins = allowedOrigins;
    this.setupMessageListener();
  }

  setupMessageListener() {
    window.addEventListener('message', (event) => {
      if (!this.allowedOrigins.includes(event.origin)) {
        console.warn('Message from unauthorized origin:', event.origin);
        return;
Enter fullscreen mode Exit fullscreen mode

🎉 Congratulations on making it to the end! If you've read this far, you're clearly someone who values depth and nuance—and I appreciate that more than you know. Whether you found this article enlightening, frustrating, or somewhere in between, I’d love to hear your thoughts. Let’s open the floor for discussion, critique, and even debate - because that’s where the real learning begins.

Top comments (0)