Microfrontends Made Simple

microfrontend architecture
scalable frontend
modular frontend
reactjs
dynamic imports
author avatar
Akash Gomsale Software Engineer @ Infocusp
11 min read  .  01 May 2025

blog banner

Introduction

Microfrontend Overview

Why Microfrontends? A Real-World Use Case

What if you were building a massive e-commerce platform that’s constantly evolving? You have multiple teams working on different parts of the site:

  • The product team manages the catalog and recommendations.
  • The checkout team handles payments and order processing.
  • The customer support team builds the help desk and live chat.

Now imagine needing to update the checkout flow in a monolithic frontend architecture. One small change could affect the entire codebase, delay deployments, and cause conflicts with other teams’ work. That’s a nightmare.

This leads to problems such as:

  • Deployment bottlenecks caused by tight interdependencies
  • Rigidity in choosing the best tools for each feature
  • Constant coordination struggles between teams stepping on each other’s toes

Microfrontends address this by splitting the frontend into smaller, independent modules that individual teams can develop and release separately.

  • The product team can use React.
  • The checkout team can work with Vue.js.
  • The customer support team might prefer Angular.

Each team develops, tests, and deploys its module separately, ensuring faster releases, minimal disruptions, and improved scalability.

Monolith vs Microfrontend Architecture

When and Why to Use Micro-Frontends

1. Scalability and Maintainability

Managing a large, monolithic frontend can be overwhelming, especially as an application expands. Micro-frontends break the system into smaller, self-contained units, making it easier to scale individual components based on demand. This modular structure also simplifies maintenance, as updates, bug fixes, or enhancements can be applied to a specific section without impacting the entire application.

2. Framework Freedom

One significant benefit of microfrontends is that different modules can be built using diverse technologies. Each module operates independently, giving teams the freedom to select the tools and frameworks that work best for their specific requirements. This flexibility allows developers to combine different technologies without concern for compatibility problems.

For example:

  • A React-based host application can integrate a Vue.js tenant module for a specific dashboard feature.

    React host integrating a Vue.js dashboard module

  • An Angular-based host application can integrate a React-based module to deliver a modern UI component.

    Angular host loading a React component

  • A Next.js host might pull in a plain JavaScript tenant to support legacy widgets.

    Next.js host pulling in a vanilla JavaScript widget

  • A Micro-frontend system could mix React, Angular, and Vanilla JS modules, allowing teams to maintain existing codebases while adopting new technologies where needed.

    Diagram showing React, Angular, and Vanilla JS modules coexisting

3. Faster Development and Deployment

With micro-frontends, different teams can work on various UI components simultaneously, eliminating dependencies that slow down development. Since each module operates independently, features can be rolled out faster without waiting for other sections to be completed. This speeds up the software development lifecycle and reduces downtime when deploying updates.

4. Enhanced Team Collaboration and Autonomy

For organizations with multiple development teams working on different parts of an application, micro-frontends promote autonomy. Each team can manage its own module independently, reducing bottlenecks and improving efficiency. By decentralizing ownership, teams can make decisions faster and align development with business goals more effectively.

When Should You Use Microfrontends?

Microfrontends are a powerful pattern, but they aren’t for everyone or every project.

Use microfrontends when:

  • You have multiple teams working on different parts of a large frontend.
  • Teams need autonomy to choose their tech stack or deploy independently.
  • The project is complex enough that modularity brings real benefits.
  • You expect frequent changes, and you want to reduce the risk of one team breaking another team’s code.

Avoid microfrontends when:

  • You're building a small or medium-sized application with a single team.
  • Your architecture doesn’t require different frameworks or separate deployments.
  • The overhead of managing multiple apps outweighs the benefits.
  • Your team isn’t familiar with the additional complexity it brings.

Known Challenges of Microfrontends

Like any architecture, microfrontends come with trade‑offs. Some known downsides include:

  • State sharing between MFEs

    Sharing data or application state (like user info or cart contents) across different microfrontends can be tricky. You’ll need to implement smart communication strategies.

  • Server‑Side Rendering (SSR)

    If SEO or performance is a concern, SSR with microfrontends can be difficult to implement, especially when mixing multiple frameworks.

  • Increased complexity

    More infrastructure, more coordination, and more setup. Microfrontends aren’t “free.” You need good DevOps practices and a solid understanding of module federation or similar technologies.




In this blog, we will walk through setting up a basic micro-frontend architecture using React. Our goal is to demonstrate how multiple micro-frontend applications can be composed together dynamically, without relying on bundlers like Webpack or transpilers like Babel. This approach will give you a deeper understanding of how micro-frontends can be structured, especially when you want to keep things lightweight and simple.


Setting Up Our Micro-Frontend Architecture

In our microfrontend setup, we have a base application that acts as the host, dynamically integrating two React applications Warrior and Gladiator. Let’s break down their roles:

  • Base Application: Serves as the central hub, seamlessly loading and managing both Applications i.e. Warrior and Gladiator.
  • Warrior: A versatile application that can function in two ways:
    • It can be hosted independently as a standalone React app.
    • It can also work as a tenant application, meaning it integrates within the base application when required.
  • Gladiator: Unlike Warrior, Gladiator is designed to run exclusively within the microfrontend architecture

Diagram of Base Application loading Warrior and Gladiator microfrontends

Let’s break down the entire process to create this setup…

1. Project Structure

# Microfrontend Project Structure

index.html  /* Entry point for Microfrontend */
app.js  
Gladiator/  
  ├── Gladiator.js  
Warrior/  
  ├── Warrior.js  
  ├── index.html  /* Entry point for Warrior application */  
  ├── warrior-scripts.js  

2. Creating the Base Application (index.html)

The index.html file serves as the main container for our micro frontend application. It defines the foundational structure, loads external React dependencies, and dynamically injects micro frontend modules. This setup ensures seamless integration of independently developed applications into a unified architecture.

Key Highlights:

  • Basic HTML Structure:
    • Establishes a clean and structured layout with a dedicated root container (#root) where microfrontends application will be injected dynamically.
  • Loading External Dependencies:
    • Includes React and ReactDOM via CDN to eliminate bundling overhead and simplify dependency management.
  • Microfrontend Application Integration:
    • Loads the main application script (app.js) to handle the rendering and integration of the microfrontend application.
  • Loading Dependencies for Warrior App:
    • Includes warrior-scripts.js, which dynamically loads additional dependencies needed for the Warrior application

index.html

… 
 <body>
   <div id="root"></div>
 
   <script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
   <script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>

   <script type="module" src="./Warrior/warrior-scripts.js"></script>
   <script type="module" src="app.js"></script>
 </body>
…

3. Base React Application (app.js)

The app.js file serves as the core of our microfrontend application. It defines the main layout, creates placeholders for each microfrontend application, and dynamically loads the microfrontend components using module imports

Key Highlights:

  • Defining Microfrontend Roots:
    • Creates #warrior-root and #gladiator-root, which act as mounting points for the respective microfrontends.
  • Building the React Layout:
    • Uses React.createElement to generate the UI dynamically without JSX.
  • Dynamically Importing Microfrontends:
    • Uses the import() function to asynchronously load Warrior and Gladiator react applications.
    • Renders imported components inside their respective mounting points.

app.js

const createElement = React.createElement;

const Header = () =>
 createElement("div", { className: "header" },
   createElement("h1", null, "Micro-Frontend Base Application"),
   createElement("p", null, "Hosting multiple independent React components")
 );

const MicroFrontends = () =>
 createElement("div", { className: "microfrontends" },
   createElement("div", { className: "module", id: "warrior-root" }),
   createElement("div", { className: "module", id: "gladiator-root" })
 );

const App = () => createElement("div", { className: "container" }, createElement(Header), createElement(MicroFrontends));

ReactDOM.render(createElement(App), document.getElementById("root"));

// Dynamically import the micro-frontends and render them
const renderMicroFrontend = (modulePath, elementId) => {
 import(modulePath).then(module => {
   const Component = module[Object.keys(module)[0]]; // Assumes default or named export
   ReactDOM.render(createElement(Component), document.getElementById(elementId));
 });
};

renderMicroFrontend('./Warrior/Warrior.js', 'warrior-root');
renderMicroFrontend('./Gladiator/Gladiator.js', 'gladiator-root');

4. Creating the Gladiator React App

The Gladiator.js file exports a simple React component that will be loaded dynamically into our host application.

Key Highlights:

  • Uses React.createElement to define a functional component.
  • Does not rely on external dependencies.
  • Will be dynamically imported into app.js.

Gladiator.js

const createElement = React.createElement;
export const Gladiator = () =>
 createElement("div", null, createElement("h1", null, "React Gladiator!"));

5. Creating the Warrior React App

5.1. Warrior Index Page (index.html)

  • The index.html file serves as the entry point for the Warrior micro-frontend.
  • The Warrior component is unique compared to the Gladiator application. It can function either as a standalone application, or as a micro-component that integrates with a base application.
  • It loads the necessary scripts and mounts the Warrior component inside the #warrior-root div.
  • External script (warrior-scripts.js) is loaded to handle dependencies and render the component.

Warrior Index Page

index.html

 …
<body>
  <div id='warrior-root'></div>

  <script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>

 
  <script type="module">
    import { Warrior } from "./Warrior.js";
    const warriorScript = document.createElement('script');
    warriorScript.src = 'warrior-scripts.js';
    warriorScript.type = 'text/javascript';
    document.head.appendChild(warriorScript);
    warriorScript.onload = function() {
      // Render once dependencies are loaded
      ReactDOM.render(React.createElement(Warrior), document.getElementById('warrior-root'));
    };
  </script>
</body>
 …

5.2. Warrior Application Component (Warrior.js)

  • This is a simple React component that displays a heading along with the current date.
  • Moment.js is used to format and display the current date.
  • Since Webpack and Babel are not used, React’s built-in createElement method is leveraged.

Warrior.js

export const Warrior = () => {
   return React.createElement(
       'div',
       null,
       React.createElement(
           'h1',
           null,
           'React Warrior!'
           + new moment().format("YYYY-MM-DD")
       )
   );
};

5.3. Loading External Dependencies (warrior-scripts.js)

  • Moment.js is added as a script element in the document head.

warrior-scripts.js

// warrior specific dependencies here.
const momentScript = document.createElement('script');
momentScript.src = 'https://unpkg.com/moment@2.29.1/min/moment.min.js';
momentScript.type = 'text/javascript';
document.head.appendChild(momentScript);

6. Running Microfrontends Locally with Python’s Simple HTTP Server

Since React scripts aren’t loading directly, we need a way to serve these applications properly. A quick and easy solution is Python’s built-in HTTP server, which allows us to run multiple applications on different ports.

6.1: Start the Server for the Main Microfrontend

  1. Open a terminal and navigate to the folder containing the index.html file of the microfrontend:

    cd /path/to/microfrontend
    
  2. Start a simple HTTP server on port 3000:

    python3 -m http.server 3000
    
  3. Now, you can access the microfrontend at: http://localhost:3000


6.2: Start the Server for the Warrior Application

  1. Open a new terminal and go to the folder of the Warrior application:

    cd /path/to/microfrontend/Warrior
    
  2. Start another HTTP server on port 3001:

    python3 -m http.server 3001
    
  3. The Warrior application will be available at: http://localhost:3001


Microfrontend Main Index Page

Conclusion

There you have it! This micro-frontend architecture demonstrates how we can structure and load independent micro-frontends without using Webpack, or Babel. We dynamically import modules, load dependencies separately, and mount them into the base application.

In the next blog, we will explore how to implement micro-frontends using React with Webpack and Babel to optimize performance and improve developer experience.

Stay tuned!

GitHub Repository: microfrontend-basic-setup 🔗