[Advanced React] Micro-Frontends Architecture: A Solution for Large Web Apps

In the ever-evolving world of software development, building and maintaining web applications is becoming increasingly complex. Traditional monolithic apps, while advantageous at first, often become bloated, hard to scale, and difficult to manage as projects grow. To solve this challenge, a new architecture inspired by the success of backend microservices has emerged—Micro-Frontends.

Micro-Frontends Architecture: A Solution for Large Web Apps

So what are Micro-Frontends, and why are they considered the future of frontend development? Let's dive in.

Micro-Frontends – A New Wave for Modern Architecture 🚀

Imagine a large website like Amazon or Netflix. Instead of building the entire UI as a single entity, Micro-Frontends architecture proposes a different approach: split the UI into smaller, manageable, independent parts.

Each part, called a "micro-frontend," is responsible for a specific feature or area of the site (e.g., search bar, shopping cart, product recommendations). They act like "mini-apps" developed, deployed, and managed by different teams independently. All these micro-frontends are then assembled to create a seamless, complete website for the end user.

Simply put, Micro-Frontends are "Microservices for the Frontend". They bring the philosophy of separation and independence from microservices to the UI world.

Why "Divide and Conquer"? The Key Benefits of Micro-Frontends

Adopting this architecture is not just a tech trend—it brings major strategic benefits for organizations and development teams.

1. Independence & Autonomy (Independent Teams)

This is the core benefit. Each team can fully own a part of the product, from database to UI. They can freely choose their own technology (React, Angular, Vue.js, etc.), architecture, and development process without affecting other teams. This boosts speed and innovation.

2. Independent Development & Deployment

Instead of redeploying the entire app for every small change, with Micro-Frontends you only redeploy the "mini-app" you modified. This is faster, reduces risk, and lets you deliver features to users more quickly.

3. Smaller, More Manageable Codebases

Each micro-frontend has its own codebase, smaller and focused on a specific business goal. This makes code easier to understand, develop, test, and maintain compared to a huge monolithic codebase.

4. Scalability & Easier Upgrades

Upgrading or replacing a part of the app becomes much simpler. You can rewrite a micro-frontend with new technology without having to "rebuild" the whole system. This keeps your app up-to-date with the latest tech.

5. Increased Resilience

If a micro-frontend fails, it only affects a small part of the app, not the whole website. This increases reliability and user experience.

Popular Micro-Frontends Integration Methods

How do you combine these micro-frontend "pieces" into a unified whole? There are several techniques, each with its pros and cons.

  • Server-Side Integration: Micro-frontends are rendered on the server and combined into a complete HTML page before being sent to the browser. Good for SEO and initial load performance.
  • Client-Side Integration with iFrames: Using <iframe> is the simplest way to embed one app into another. It provides great CSS and JS isolation but can cause SEO, accessibility, and communication issues.
  • Client-Side Integration with JavaScript: Each micro-frontend is bundled as a JavaScript file. The "container" app loads and mounts these micro-frontends at the right places on the page. This is the most flexible and popular approach today.
  • Module Federation (Webpack 5): A breakthrough feature of Webpack 5, allowing different JavaScript apps to share and use each other's code at runtime. It's considered one of the best solutions for Micro-Frontends today.

When (and When Not) to Use Micro-Frontends?

Despite many benefits, Micro-Frontends are not a "silver bullet" for every project. Adopting this architecture comes with its own challenges.

Consider Micro-Frontends if:

  • You have a large, complex web app.
  • You have multiple development teams working in parallel.
  • You want to speed up development and deployment.
  • Your app needs frequent upgrades and maintenance.

Be cautious if:

  • Your project is small and simple.
  • Your team is small.
  • You lack experience with distributed systems.

Managing, deploying, and ensuring a consistent user experience across micro-frontends are challenges that need careful consideration.

Example: Micro-Frontends with React & Module Federation

Before wrapping up, let's look at a practical example. Imagine building a simple e-commerce site. Instead of a single React app, we'll split it into:

  1. Host App: The main app, acting as the "container" for other parts. It manages the common layout and navigation.
  2. Micro-Frontend App: An independent React app responsible for displaying the product list. Let's call it products.

Our goal: The container app can display the ProductListPage component from the products app without installing products as a dependency.

Step 1: Configure products (Micro-Frontend App)

This app will "expose" its component.

  1. Install Webpack:

    npm install webpack webpack-cli webpack-dev-server html-webpack-plugin --save-dev
    
  2. Create a simple React component:

    // src/ProductListPage.js
    import React from 'react'
    
    const ProductListPage = () => (
      <div>
        <h2>This is the MFE Product List Page</h2>
        <ul>
          <li>Product A</li>
          <li>Product B</li>
          <li>Product C</li>
        </ul>
      </div>
    )
    
    export default ProductListPage
    
  3. Configure webpack.config.js to "expose" the component:

    const HtmlWebpackPlugin = require('html-webpack-plugin')
    const { ModuleFederationPlugin } = require('webpack').container
    const deps = require('./package.json').dependencies
    
    module.exports = {
      // ... other configs
      mode: 'development',
      devServer: {
        port: 3001, // Run this MFE on port 3001
      },
      plugins: [
        new ModuleFederationPlugin({
          name: 'products',
          filename: 'remoteEntry.js',
          exposes: {
            './ProductListPage': './src/ProductListPage',
          },
          shared: {
            ...deps,
            react: { singleton: true, requiredVersion: deps.react },
            'react-dom': {
              singleton: true,
              requiredVersion: deps['react-dom'],
            },
          },
        }),
        new HtmlWebpackPlugin({
          template: './public/index.html',
        }),
      ],
    }
    

Key points:

  • name: 'products': Name of this micro-frontend.
  • filename: 'remoteEntry.js': File that the container app will use to discover what's exposed.
  • exposes: {'./ProductListPage': ...}: Exposes the ProductListPage component.

Step 2: Configure container (Host App)

This app will "consume" the component from products.

  1. Configure webpack.config.js to "receive" the component:

    const HtmlWebpackPlugin = require('html-webpack-plugin')
    const { ModuleFederationPlugin } = require('webpack').container
    const deps = require('./package.json').dependencies
    
    module.exports = {
      // ... other configs
      mode: 'development',
      devServer: {
        port: 3000, // Run Host App on port 3000
      },
      plugins: [
        new ModuleFederationPlugin({
          name: 'container',
          remotes: {
            products: 'products@http://localhost:3001/remoteEntry.js',
          },
          shared: {
            ...deps,
            react: { singleton: true, requiredVersion: deps.react },
            'react-dom': {
              singleton: true,
              requiredVersion: deps['react-dom'],
            },
          },
        }),
        new HtmlWebpackPlugin({
          template: './public/index.html',
        }),
      ],
    }
    

Key points:

  • remotes: { products: '...' }: Declares that container wants to use a micro-frontend named products, found at http://localhost:3001/remoteEntry.js.
  1. Use the shared component in container:

    Now you can import the component from products as if it were part of the container app.

    // src/App.js
    import React, { Suspense } from 'react'
    
    // Dynamically load the component from remote 'products'
    const RemoteProductListPage = React.lazy(
      () => import('products/ProductListPage'),
    )
    
    const App = () => {
      return (
        <div>
          <h1>This is the Container App</h1>
          <nav>
            <a href="/">Home</a> | <a href="/products">Products</a>
          </nav>
          <hr />
          <h2>The Micro-Frontend content will appear below:</h2>
          <Suspense fallback={<div>Loading products...</div>}>
            <RemoteProductListPage />
          </Suspense>
        </div>
      )
    }
    
    export default App
    

Key points:

  • React.lazy(() => import('products/ProductListPage')): Special syntax. products is the remote name defined in Webpack, and ProductListPage is the exposed component.
  • <Suspense>: Since the component is loaded over the network, use Suspense to show a loading message while waiting.

Result

Now, run both apps at the same time:

  • In the products folder, run npm start (runs on port 3001).
  • In the container folder, run npm start (runs on port 3000).

When you visit http://localhost:3000, you'll see the container app's UI. Inside it, the product list from the products app (running on port 3001) is loaded and displayed seamlessly.

The magic is that the products team can update, fix, and redeploy their app without the container team having to do anything. The next time a user reloads the page, they'll automatically get the latest version of the products micro-frontend.

This is the power of independence and flexibility that Micro-Frontends architecture brings.

Conclusion: The Future of Frontend

Micro-Frontends architecture represents a major shift in how we build web apps. By "dividing and conquering," it enables organizations to build large, complex products more flexibly, sustainably, and efficiently.

While not a solution for every problem, with the rise of tools like Module Federation, Micro-Frontends will continue to be a leading trend shaping the future of frontend architecture.

Related Posts

[Advanced React] Virtual DOM in React: The Foundation of Performance and How It Works

A deep dive into the Virtual DOM in React—one of the most important concepts for fast rendering. Understand how it works, its key advantages, and why it’s so popular.

[Advanced React] What is React Fiber? Understanding Its Architecture and How It Works

Learn about React Fiber, the core architecture that makes React faster and smoother. Discover how it works, its benefits, and its importance in optimizing React app performance.

[Advanced React] React Component Composition: Effective Advanced Techniques

Master advanced component composition techniques in React. This article will help you refactor components efficiently, make them more extensible, and manage your code better.

[Advanced React] Understanding Render Phase and Commit Phase to Optimize React Performance

Have you heard of the Render Phase and Commit Phase? Don’t miss this article to deeply understand how React handles UI updates, so you can improve performance and write more effective React code.