Exploring Micro Frontends: A Comprehensive Guide

Exploring Micro Frontends: A Comprehensive Guide

As we navigate through the bustling streets of web development, a new hero emerges from the shadows: micro frontends. This isn’t just another tech buzzword to add to your collection but a game-changing approach to redefine how we build, deploy, and maintain our digital storefronts. Imagine breaking down the Great Wall of monolithic applications into smaller, nimbler, and more manageable pieces—this is the promise of micro frontends. It’s like assembling a team of superheroes, each with unique powers, working together to save the day or, in our case, making the web a better place. Through this article, we’ll embark on an adventure to explore how micro frontends can empower teams to innovate faster, reduce complexities, and deliver exceptional user experiences. So buckle up and prepare to dive into the world of micro frontends, where the future of web development is modular, flexible, and surprisingly fun.

Understanding Micro Frontends

Micro frontends are an architectural style where a frontend app is decomposed into individual, semi-independent “microapps” working loosely together. This concept extends the microservices paradigm to frontend development, aiming to increase the scalability and flexibility of large web applications. It allows different teams to work on different features or parts of a website with a certain degree of autonomy, using different frameworks or technologies if desired and then integrating them into a cohesive user experience.

Historical Context and Evolution

Monolithic Frontends

Traditionally, web applications were built as monolithic entities, where both frontend and backend were tightly integrated. This straightforward approach led to scalability and maintainability issues as applications grew.

Rise of Microservices

Backend development shifted towards breaking down applications into smaller, independently deployable services with the advent of microservices. This approach improved scalability, flexibility, and the speed of development for backend systems.

Extension to Frontend

Inspired by the success of microservices, micro frontends emerged as a way to apply these principles to frontend development. It allowed for parts of web applications to be developed, tested, and deployed independently.

Identifying Challenges

Complexity in Scaling and Maintaining Large Applications

As web applications grow, their codebases become complex and cumbersome, making them difficult to scale and maintain. Changes in one part of the application can have unintended consequences on other parts, leading to a fragile overall system.

Slow Development Cycles

In a monolithic architecture, the intertwined nature of frontend and backend code can lead to slower development cycles. Teams often have to wait for one part of the system to be updated before proceeding with changes in another.

Team Collaboration Issues

Large teams working on a monolithic front end often step on each other’s toes. Different teams may have preferences for certain technologies and frameworks but are forced to conform to a single choice, which can stifle innovation and reduce efficiency.

Difficulties in Adopting New Technologies

Incorporating new technologies into a monolithic front can be risky and time-consuming, as it might require significant changes across the entire application, potentially disrupting existing functionalities.

Solution

Decomposition into Micro Frontends

Each team can work independently on their application segment by breaking the front end into smaller, manageable pieces (micro frontends). This modularity allows for easier scaling, better maintenance, and the ability to develop and deploy parts of the application in isolation.

Increased Development Speed

Micro frontends enable parallel development. Teams can work on different features simultaneously without waiting for other application parts. This approach significantly speeds up development cycles and reduces time-to-market.

Enhanced Team Autonomy

Teams can choose the technologies and frameworks that best suit their component’s needs. This autonomy boosts innovation and enhances job satisfaction among developers who can work with technologies they are most comfortable with.

Seamless Integration of New Technologies

Integrating new technologies becomes less disruptive with micro frontends. Teams can experiment with new tools in their respective areas without the risk of impacting the entire system.

Implementation

A demo project of a shopping platform was created to demonstrate micro frontends. The framework chosen for its creation was single-spa, a router for front-end microservices that allows different JavaScript apps to coexist on a page without refreshing the page. Single-spa is particularly beneficial because it handles the activity functions of apps and lets you use multiple JavaScript frameworks in a single-page application, ensuring flexibility and developer freedom. It stands out from other micro frontend tools by offering a straightforward way to connect multiple frameworks cohesively and efficiently, avoiding the complexities of managing multiple frontend frameworks manually.

Within the project, we can identify four primary folders—the most significant being the root app configuring the micro frontends. The root app essentially acts as the orchestrator for the micro frontends, determining which app to display based on the route and user interactions. It’s where the micro frontends are registered, and the application shell is managed. The registration process defines the conditions under which each micro frontend is active, allowing seamless navigation and interaction within the single-page application framework.

Within the single-SPA framework, the term ‘application’ refers to any micro frontend that is a complete SPA but designed to function as part of a larger composition. In our case, the three applications, or micro frontends, are a login-app, a shopping-app, and a card-app.

The login-app functions as a self-contained application dedicated to user authentication. It presents the user interface for login.

The shopping-app is a distinct application within the ecosystem, handling the product discovery and selection phase of the shopping experience.

Lastly, the card-app serves as a payment application, dealing with everything related to financial transactions. It also manages the shopping cart.

In the single-spa framework, these ‘applications’ are technically a SPA that could stand alone. However, when brought into the single-spa environment, they are initialized, mounted, and unmounted according to the user’s navigation and interaction. They behave as part of a cohesive whole without needing page reloads. This modular approach allows developers to update one application without redeploying the entire front end, deploy them independently, and scale them according to individual traffic and feature requirements. This leads to an agile development environment, where real-time updates, experiments, and improvements can occur with minimal impact on the overall user experience.

This project architecture not only exemplifies the practicality of micro frontends but also underscores how a single SPA facilitates the management of multiple SPAs as a unified platform, enhancing developers’ capabilities and providing a robust user experience.

This project structure is emblematic of the micro frontend architecture’s core advantages: independent development, deployment, and scaling of features within a complex application ecosystem. It showcases the real-world applicability and effectiveness of micro frontends in simplifying the development and maintenance of large-scale web applications.

The screen of the main folder:

An additional library—single-spa-layout — was used to configure the project. This library is a powerful tool in the single-spa ecosystem designed to simplify and enhance the layout configuration for micro frontend architectures.

Single-spa-layout allows developers to define the layout of their micro frontends declaratively using straightforward syntax. This is crucial because as applications grow and more micro frontends are added, managing how and where each should be displayed on the page becomes increasingly complex.

With single-spa-layout, developers can outline a ‘blueprint’ of the application layout using a single configuration file. This file acts as a map, telling the single-spa root application where to place each micro frontend in the overall layout. It allows for dynamic routing and placement of micro frontends without hardcoding them into the main application, enabling a much more flexible and maintainable approach to layout design.

Moreover, single-spa-layout streamlines the creation of nested routes and layouts, making it easier to manage complex view hierarchies. It is especially useful in ensuring consistent and reliable loading sequences and handling errors and transitions between different micro frontends.

By utilizing a single-spa-layout, the project gains an organized, scalable, and maintainable structure for its micro frontends. This allows for improved developer workflow and a more robust and adaptable application architecture.

Screen from root config:

In the provided screenshot of oril-root-config.ts, a TypeScript configuration file for a single-spa micro frontend architecture, several key actions are being performed to set up the application:

Importing Necessary Functions and Layouts:

The registerApplication and start functions are imported from the single-spa package. These functions are essential for registering each micro frontend with the single-spa root application and starting the application, respectively.

The single-spa-layout package imports the functions constructApplications, constructRoutes, and constructLayoutEngine. These functions are part of the single-spa-layout library and help organize the layout of micro frontends and their routing logic.

There is also an import of microfrontendLayout from a local HTML file, which presumably contains the declarative layout configuration for the micro frontends.

Constructing Routes and Applications:
The constructRoutes function takes the imported microfrontendLayout and creates a set of route objects. These routes define where and when the micro frontends should be displayed within the app.

The constructApplications function then takes these routes and defines the applications that will be loaded and when. It uses a loadApp function to asynchronously load the micro frontends using the SystemJS import map, a standard for loading ES modules in browsers.

Initializing the Layout Engine:

With routes and applications defined, the constructLayoutEngine function creates a layout engine instance. This engine uses the information from routes and applications to manage the placement and activation of micro frontends on the page.

Registering Applications and Starting the Root App:

The applications.forEach(registerApplication) loop registers all the defined applications with the single-spa root application. This is essential for the applications to be recognized and managed by the root.

The layoutEngine.activate() call activates the layout engine, ensuring it listens for route changes and displays the appropriate micro frontends accordingly.
Finally, the start() function call kicks off the single-spa root application, enabling the initialization and rendering of the micro frontends according to the configured routes and layout.
In summary, this configuration file is central to setting up and managing micro frontends in a single-spa application. It defines how they are loaded, where they are placed on the page, and how they are activated based on user navigation.

Configuring the Layout

Screen from the layout file:

The microfrontend-layout.html file is a key part of the single-spa project, which uses the single-spa-layout library to manage the layout of micro frontends.

Here’s a breakdown of what’s happening in the file:

Defining the Router: The <single-spa-router> element with the attribute mode=”history” indicates that the application uses the HTML5 history API to navigate micro frontends. This mode allows for clean URLs without hashbangs (#) and improves user experience and SEO. The base=”” attribute defines the base URL for all locations. An empty string means that the base URL is the application’s root.

Setting Up Routes: Inside the <single-spa-router>, there are <route> elements that define the path for each micro frontend application. Each route is associated with a different micro front end, and the path attribute specifies the URL path that will activate the corresponding micro front end.
Registering Applications: Within each <route>, there is an <application> element with a name attribute. This attribute specifies the name of the micro frontend that should be active and displayed when the URL matches the given path.

The first route has the path / (the root path) and activates the @ori/login-app. This means that when users navigate to the base URL of the application, the login-app is displayed.

The second route has the path /shop and is associated with the @ori/shopping-app. When users navigate to /shop, the shopping-app is the active micro frontend.

The third route has the path /card for the @ori/card-app, which becomes active when the /card path is visited, typically for handling shopping cart and checkout processes.

This layout file effectively maps URLs to micro frontends, allowing the single-spa root application to display the correct micro frontend based on the current browser URL. It helps orchestrate the micro frontend navigation in a declarative way, making the project easier to maintain and update. The single-spa-layout library abstracts away the direct manipulation of the DOM, providing a more structured and readable way to define the layout and routing for micro frontends.

After configuring the project, we created the micro frontends for our application. The login-app and shopping-app are projects that use the React library as their base, while the card-app was built using the Vue framework. Here’s an example of the configuration for the login-app:

The key elements in this file are:

Imports: The file begins by importing the necessary libraries:
React and ReactDOM, from their respective packages, use React to build the UI.
singleSpaReact from the single-spa-react package is a utility for integrating React with single-spa.

The root component from a local file is likely the top-level React component for the login-app.

Lifecycle Hooks Setup: The lifecycles constant is created by calling singleSpaReact with an object that specifies React, ReactDOM, the root component (Root), and an error boundary function.
errorBoundary: This is a function that React will call when there’s an error during rendering, in a lifecycle method, or the constructor of any child component. In this file, the error boundary is a simple function that takes err, info, and props as arguments and returns null, indicating that it’s not rendering any fallback UI in the event of an error.

Exporting Lifecycles: Finally, the file exports the bootstrap, mount, and unmount lifecycle functions destructured from the lifecycles object. These functions are crucial for the single-spa to initialize, render, and unmount the login-app micro frontend when appropriate.

After all the micro frontends have been created, they need to be added to the index.ejs file in the root-app. The index.ejs file serves several vital roles in a single-spa micro frontend architecture:

Serving as the Entry Point: The index.ejs file is essentially the entry point of the single-SPA application. When a user visits the app, the initial HTML page is loaded. This file must include all the necessary setup scripts and link tags to bootstrap and run the micro frontends.

Loading Micro Frontends: The file typically uses SystemJS import maps to define where to load the micro frontend applications. These would be local servers in a development environment, as defined in the import map within the index.ejs. In a production environment, the import map would point to the deployed URLs of the micro frontends.
Initializing single-spa: It will also initialize single-spa itself, loading the library and starting the application. This includes bootstrapping the root configuration that handles the micro frontends’ registration and activity functions.

Defining Global Layout: Often, the index.ejs will include global styles or scripts that apply to all micro frontends. It may also define the structural layout within which the micro frontends will be placed as they are loaded and activated based on route changes.

Handling Environment Differences: The file can handle configurations for various environments (development, staging, production) using templating conditions. For instance, it might include additional debugging tools or scripts in development.

Preloading Resources: Optionally, it can preload essential resources to optimize performance. This includes preloading critical CSS or JavaScript files needed across all micro frontends.

Screen from index.ejs file:

Running the Application

After completing the setup of the micro frontends and adding them to the index.ejs file in the root-app, the next step is to launch the application to see everything in action. To do this, you must navigate to the root-app directory and execute the command npm start, which will start the application. Here’s what happens when you run this command:

Starting a Local Development Server: The npm start command typically triggers a script defined in the root app’s package.json file. This script usually starts up a local development server.
Building the Application: If there is a build step involved, the command may also compile the TypeScript or JavaScript code, bundle it with tools like Webpack, and transpile any JSX into regular JavaScript if you’re using React.

Serving the Root Application: The root application, which acts as the orchestrator for all the micro frontends, will be served to the browser. It’s responsible for loading the initial HTML document (index.ejs), including the single-spa setup and the import map for the micro frontends.

Initializing Micro Frontends: As you navigate the application, single-spa will load the appropriate micro frontends based on the current route. The configuration set in the root app handles this, determining which micro frontend should be active for a given path.

Development Feedback Loop: Running the application this way also benefits development. It provides a live feedback loop where you can see your changes in real-time, thanks to features like hot module replacement if configured.

It’s important to note that if you have multiple micro frontends running on different ports or servers, you might need to start each one individually. However, the root app typically orchestrates them as a cohesive unit. Setting up a local development environment with npm start ensures that developers can work on the application and test features before deploying to a staging or production environment.

Screen from our default route:

 

References

https://github.com/oril-software/microfrontends

Monolithic vs. Microservices: Choosing the Right Application Architecture for Your Business

It shouldn’t come as a surprise that the type of app architecture you select for your future product can be a game changer. Your choice will define scalability, maintenance cost, and the speed of new feature implementation. Two popular approaches have gained a lot of attention in recent years in the app development landscape. These […]

Ihor Kosandyak Avatar
Ihor Kosandyak

6 Jun, 2023 · 6 min read

Microservices with Java. Quick Start. Part 1

Hi everyone! In this article we will create the simple Spring Cloud Project ready to start and work! Day by day, often and often you can hear about microservices architecture. Everybody wants to create services that are independent from each other. And failure of the single service doesn’t lead to the whole application failure. Of course, microservices […]

Ihor Kosandyak Avatar
Ihor Kosandyak

5 Nov, 2017 · 7 min read

Microservices with Java. Quick Start. Part 2

Hello! In this part we will speak about communication between microservices, and will add an API Gateway to our project. If you missed the first part of this topic, you may find it here. When you build an application with microservice architecture, you will definitely have couple services running separately. Of course you will need them […]

Ihor Kosandyak Avatar
Ihor Kosandyak

5 Nov, 2017 · 5 min read