+359 888 271 714[email protected]
B
BuildifyerDigital Growth
Web Development

Micro-Frontends – Scalable Web Architecture for Large Teams in 2026

Buildifyer··18 min read

What Are Micro-Frontends?

Micro-frontends extend the microservices philosophy to the frontend layer. Instead of building and deploying one monolithic single-page application, you split the user interface into smaller, self-contained applications — each owned by a different team, each with its own repository, build pipeline, and deployment schedule. The user sees a seamless product; behind the scenes, multiple independent units collaborate to compose the page.

The concept emerged around 2016 from organisations like Zalando and IKEA that were scaling backend microservices but found their frontend remained a bottleneck. A single React or Angular monolith meant that all feature teams had to coordinate releases, shared global state created coupling, and a bug in one team's code could bring down the entire application. Micro-frontends solve this by giving each team end-to-end ownership from the database to the UI component.

By 2026, micro-frontends have matured from an experimental pattern into a mainstream architecture choice supported by robust tooling — Module Federation, single-spa, Piral, Native Federation, and build-time composition strategies. However, this architecture is not free: it introduces complexity in routing, shared state, consistent styling, and performance optimisation. Understanding when and how to adopt it is critical.

Why Micro-Frontends? The Problem with Monoliths

A monolithic frontend works well for small teams and early-stage products. But as the organisation grows, several pain points emerge:

Deployment Coupling

In a monolith, every feature lives in the same codebase. If Team A's checkout flow is ready to ship but Team B's product page has a blocking bug, both wait. Release trains become slow and risky because they bundle unrelated changes together.

Build Time

A large React or Next.js application can take 10–30 minutes to build. Every PR triggers a full rebuild and test suite. CI costs rise, developer feedback loops lengthen, and engineers spend more time waiting than coding.

Codebase Complexity

Thousands of components, hundreds of shared utilities, tangled import graphs — the monolith becomes a maze. Onboarding new developers takes weeks. Refactoring is risky because changes to shared code have unpredictable downstream effects.

Technology Lock-in

Migrating from Angular to React (or React to something newer) in a monolith is an all-or-nothing proposition. With micro-frontends, you can migrate incrementally — one section at a time — running old and new frameworks side by side.

Team Autonomy

Micro-frontends let each team choose the tools and release cadence that suits their domain. The checkout team can use React with a weekly release cycle while the analytics dashboard team uses Svelte with continuous deployment. Autonomy increases ownership and velocity.

Implementation Strategies

There is no single way to build micro-frontends. The right approach depends on your runtime requirements, team structure, and performance budget.

Build-Time Integration

Each micro-frontend is published as an npm package. A shell application imports them at build time and produces a single optimised bundle.

Pros: Simple mental model, single deployment artifact, standard bundler optimisations (tree shaking, code splitting).

Cons: Teams still need to coordinate releases because the shell rebuilds whenever a dependency updates. Not truly independent deployment.

{
  "dependencies": {
    "@myorg/checkout-mfe": "^2.1.0",
    "@myorg/product-mfe": "^3.4.0",
    "@myorg/search-mfe": "^1.8.0"
  }
}

Iframes

Each micro-frontend runs inside its own <iframe>. Complete isolation is guaranteed — separate DOM, CSS, and JavaScript context.

Pros: Strongest isolation, impossible for one MFE to break another, any framework can be used.

Cons: Poor UX (no shared scroll, accessibility challenges, no seamless navigation), performance overhead from loading separate HTML documents, difficult cross-frame communication.

Iframes are rarely the primary strategy in 2026 but remain useful for embedding third-party widgets or legacy applications that cannot be otherwise integrated.

Web Components

Each micro-frontend exposes its root as a Custom Element. The shell application renders <checkout-mfe></checkout-mfe> in the page, and the browser's Shadow DOM provides style encapsulation.

Pros: Framework-agnostic, standard browser API, good style isolation via Shadow DOM.

Cons: Shadow DOM complicates global theming, hydration for SSR is non-trivial, event bubbling across shadow boundaries requires care.

class CheckoutMFE extends HTMLElement {
  connectedCallback() {
    const shadow = this.attachShadow({ mode: 'open' });
    const root = document.createElement('div');
    shadow.appendChild(root);
    ReactDOM.createRoot(root).render(<CheckoutApp />);
  }
}

customElements.define('checkout-mfe', CheckoutMFE);

Module Federation (Runtime Integration)

Module Federation is the dominant approach in 2026. Introduced in webpack 5, it allows separate builds to expose and consume modules at runtime. A host application declares remote entry points; when the user navigates to a section, the browser fetches the remote's JavaScript bundle and mounts its components.

Pros: True independent deployment, runtime composition, shared dependencies to avoid duplication, works with React, Vue, Angular, Svelte.

Cons: Configuration complexity, version skew between shared libraries, runtime errors if a remote is unavailable.

Module Federation Deep Dive

Module Federation treats each micro-frontend as a container that can both expose modules and consume modules from other containers. The configuration lives in the bundler config.

Host Configuration (Shell App)

const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'shell',
      remotes: {
        checkout: 'checkout@https://checkout.example.com/remoteEntry.js',
        product: 'product@https://product.example.com/remoteEntry.js',
      },
      shared: {
        react: { singleton: true, requiredVersion: '^18.0.0' },
        'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
      },
    }),
  ],
};

Remote Configuration (Checkout MFE)

const { ModuleFederationPlugin } = require('webpack').container;

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

Loading a Remote Component

const CheckoutApp = React.lazy(() => import('checkout/CheckoutApp'));

function App() {
  return (
    <React.Suspense fallback={<div>Loading checkout...</div>}>
      <CheckoutApp />
    </React.Suspense>
  );
}

Native Federation and Vite

For projects using Vite or Rspack, the Native Federation library (@softarc/native-federation) brings Module Federation semantics without webpack. The Vite plugin for Module Federation (@module-federation/vite) is also actively maintained. In 2026, both tools are production-ready and offer faster build times than webpack for most projects.

Routing and Communication Patterns

Routing

The shell application owns top-level routes. Each route maps to a micro-frontend:

/checkout/*  → Checkout MFE
/products/*  → Product MFE
/dashboard/* → Dashboard MFE

The shell uses a client-side router (React Router, Vue Router) to mount the correct remote component. Within its route prefix, each MFE manages its own sub-routes independently.

For server-side composition, tools like Podium (from Finn.no) or edge-side includes (ESI) assemble HTML fragments from different services before sending the page to the browser. This approach delivers faster Time to First Byte and better SEO.

Communication

Micro-frontends should communicate through loosely coupled mechanisms:

  • Custom Eventswindow.dispatchEvent(new CustomEvent('cart-updated', { detail: { itemCount: 3 } })). Other MFEs listen for this event.
  • URL state — query parameters and hash fragments as a shared contract.
  • Shared store — a lightweight pub/sub or a small Redux/Zustand store loaded once by the shell and injected into each MFE.
  • Props down, events up — the shell passes data to MFEs via component props; MFEs emit events to notify the shell of changes.

The golden rule: micro-frontends should never import code directly from another micro-frontend. All shared code belongs in a published package or is injected by the shell.

Shared State and Styling

State Management

Sharing global state (authenticated user, locale, theme) is necessary but must be carefully scoped. The shell maintains the global state and passes it via context or props. Individual MFEs manage their own local state independently.

A pattern that works well is an event-driven state bus:

const bus = new EventTarget();

// Shell sets user
bus.dispatchEvent(new CustomEvent('auth', { detail: { user } }));

// MFE reads user
bus.addEventListener('auth', (e) => setUser(e.detail.user));

Styling

CSS conflicts are the most common micro-frontend headache. Strategies include:

  • CSS Modules or CSS-in-JS — scoped class names prevent collisions.
  • Shadow DOM — strongest encapsulation but limits global theme access.
  • BEM or naming conventions — each MFE prefixes its classes (e.g., checkout__button, product__card).
  • Design tokens — a shared package distributes CSS custom properties (--color-primary, --spacing-md). Each MFE consumes tokens but defines its own component styles.

The design system should live in a shared library that all MFEs depend on. This ensures visual consistency while allowing independent development.

Testing Micro-Frontends

Testing becomes more nuanced in a micro-frontend architecture:

Unit Tests

Each MFE runs its own unit tests in isolation using Jest, Vitest, or Testing Library. This is no different from testing a normal application.

Integration Tests

Test the MFE in the context of the shell. Use Cypress or Playwright to navigate to the route that loads the MFE and verify that data flows correctly across the boundary.

Contract Tests

Define a contract (expected props, events, API endpoints) between the shell and each MFE. Tools like Pact can automate contract verification, ensuring that a new MFE version does not break the shell's expectations.

End-to-End Tests

Run E2E tests against the fully composed application. Since MFEs are deployed independently, your E2E suite should test production-like URLs where all remotes are resolved.

test('user can complete checkout', async ({ page }) => {
  await page.goto('https://staging.example.com/products/1');
  await page.click('[data-testid="add-to-cart"]');
  await page.goto('https://staging.example.com/checkout');
  await page.fill('[data-testid="card-number"]', '4242424242424242');
  await page.click('[data-testid="pay-button"]');
  await expect(page.locator('.order-confirmation')).toBeVisible();
});

Real-World Examples

IKEA

IKEA's website serves millions of users across dozens of markets. Their frontend is composed of micro-frontends, each handling a different domain: product listing, cart, checkout, store finder. Teams in different countries can localise and deploy their section independently without coordinating global releases.

Spotify

Spotify's desktop application uses a micro-frontend-like architecture where individual UI sections (playlists, search, now-playing, library) are developed by separate teams. Each section is an iframe-based module that communicates through a well-defined API layer.

Zalando

Zalando, one of the pioneers of micro-frontends, split their fashion e-commerce platform into team-owned fragments. They built Project Mosaic, an open-source composition framework, to assemble page layouts from independently deployed services.

SAP

SAP's Luigi framework enables micro-frontend composition for enterprise applications. It provides a shell with configurable navigation, authentication, and localisation, while feature teams deliver individual micro-frontends.

Pros and Cons Summary

Advantages

  • Independent deployments — ship features without waiting for other teams.
  • Team autonomy — each team owns their domain end-to-end.
  • Incremental migration — adopt new frameworks one section at a time.
  • Fault isolation — a crash in one MFE does not bring down the whole page.
  • Smaller codebases — each MFE is easier to understand, test, and maintain.
  • Faster CI/CD — builds are scoped to individual MFEs, reducing pipeline duration.

Disadvantages

  • Operational complexity — more repositories, more pipelines, more infrastructure.
  • Performance risk — duplicate frameworks, extra network requests, increased bundle size.
  • Consistent UX — harder to maintain a unified design without a strong design system.
  • Cross-cutting concerns — authentication, analytics, error tracking must be coordinated.
  • Developer experience — running multiple MFEs locally for development can be cumbersome.
  • Debugging — tracing issues across MFE boundaries is more difficult than in a monolith.

When NOT to Use Micro-Frontends

Micro-frontends are not a silver bullet. Avoid them when:

  • You have a small team (fewer than 3–4 frontend developers). The overhead of multiple repositories and deployment pipelines outweighs the benefits.
  • The product is simple — a marketing site, a blog, or a single-purpose tool does not need architectural decomposition.
  • You lack platform engineering capacity — micro-frontends require shared infrastructure: a shell app, a design system, CI/CD templates, monitoring. Without dedicated platform support, teams will reinvent the wheel.
  • Performance is the top priority — a well-optimised monolith will always outperform a poorly managed micro-frontend setup. If your user base is on slow networks or low-end devices, every extra kilobyte of framework duplication matters.

The best advice: start with a well-structured monolith. Split into micro-frontends only when organisational scaling pain becomes the dominant bottleneck. Premature decomposition creates complexity without delivering proportional value.

Conclusion

Micro-frontends offer a powerful architectural pattern for organisations where multiple teams need to develop, test, and deploy frontend features independently. In 2026, tools like Module Federation, Native Federation, and frameworks like single-spa and Piral have made the pattern accessible and production-proven. However, the complexity cost is real — duplicate bundles, routing coordination, shared state, and consistent styling all require deliberate engineering.

The decision to adopt micro-frontends should be driven by team structure and deployment needs, not by technical novelty. If your organisation has outgrown the monolith and independent deployment is a genuine requirement, micro-frontends are a proven solution. If your team is small and ships comfortably from a single codebase, keep it simple.

Need help? Contact us.

micro-frontendsweb architectureModule Federationscalable web appsfrontendmonolith

Frequently asked questions

What are micro-frontends?

Micro-frontends apply the microservices principle to the frontend layer. Instead of one monolithic codebase, the UI is split into smaller, independently developed and deployed applications — each owned by a separate team and potentially built with a different framework.

When should I use micro-frontends?

Micro-frontends make sense when multiple teams work on the same product and need to release independently, when the codebase has grown too large for a single build pipeline, or when you need to migrate from a legacy framework incrementally without a full rewrite.

What is Module Federation?

Module Federation is a webpack 5 (and now rspack/Vite) plugin that allows separate builds to share code at runtime. One application can dynamically load components from another without rebuilding. It is the most popular technical approach for micro-frontend composition in 2026.

Do micro-frontends hurt performance?

They can if not managed carefully. Duplicate framework bundles, extra network requests, and uncoordinated loading can increase bundle size and time-to-interactive. Shared dependency strategies, lazy loading, and server-side composition mitigate most performance concerns.

How do micro-frontends communicate?

Common patterns include Custom Events on the window object, a shared event bus, URL/query parameters for routing, and a lightweight shared state store. The key principle is loose coupling — micro-frontends should minimise direct dependencies on each other.

Related Articles

CI/CD for web projectsWeb Development

CI/CD for Web Projects – Basics and First Steps

What CI/CD is, why it matters for web development, and how to automate tests and deploy with GitHub Actions or similar.

10 min readRead article
Docker basics for web developmentWeb Development

Docker Basics for Frontend and Web Development

What Docker is, why it's useful for web development, and how to run a project in a container – basics for developers.

11 min readRead article

Get a free consultation for your project

Contact us and we'll plan specific tasks for next month with measurable results.

Call nowViber