Introduction to Microfrontends

Introduction to Microfrontends

Microfrontends have emerged as a revolutionary architectural pattern in web development, providing a solution to the limitations of monolithic frontends. Traditionally, web applications were built as a single and tightly coupled unit. Microfrontends offer a fresh perspective to web development by breaking down the front end into smaller, independent units that can be developed and deployed separately.

The central concept behind microfrontends is to divide the frontend into multiple self-contained modules, each responsible for a specific functionality or feature of the application. These modules can be developed using different technologies, frameworks, or programming languages, allowing teams to choose the most suitable tools for their specific requirements. This modular approach promotes flexibility, scalability, and independent development.

In this blog post, we will understand the concept of microfrontends along with the architecture, frameworks, and types of integration it offers.

What are microfrontends?

Microfrontends are an architectural pattern in web development that offers an alternative approach to building frontend applications. In this pattern, the front end of an application is divided into smaller, independent units or modules. Each module, known as a microfrontend, can be developed, deployed, and maintained separately. Every microfrontend represents a specific functionality or feature of the application and can be developed using different technologies, frameworks, or programming languages.

Monolith vs Microfrontend

(Monolith vs Microfrontend)

Why microfrontends are needed?

The need for microfrontends arises from the limitations of monolithic frontends as applications grow in complexity and size. In the monolithic frontends, all components and features are interconnected & dependent on each other, making it challenging to scale, maintain, and update the application as it grows. They are built using a single technology stack, making adopting new tools or frameworks difficult. Incorporating new technologies requires significant refactoring, hindering innovation and using more efficient, modern, and suitable technologies for specific parts of the application.

Here are some of the benefits of using microfrontends that make it essential to use over monolithic frontends:

  1. Modular and independent development: Microfrontends allow different teams to work on different parts of the application independently. Each team can choose the most suitable technologies and frameworks for their microfrontends, promoting flexibility and innovation. This modular approach enables faster and parallel development cycles, as teams can work on their respective microfrontends without affecting others.

  2. Scalability and maintainability: Microfrontends provide a scalable solution for large and complex applications. Each microfrontend can be scaled independently based on specific needs, allowing for efficient resource allocation. Additionally, the modular structure makes maintenance easier as each microfrontend is self-contained and can be understood and modified without affecting the entire application.

  3. Technology flexibility: With microfrontends, teams can adopt different technologies and frameworks based on their requirements. This enables using the most suitable tools for each microfrontend, promoting innovation, and leveraging the strengths of different technologies within a single application.

  4. Code reusability and consistency: Microfrontends facilitate code reusability and sharing of components and libraries across different parts of the application. This promotes consistency in the user interface and functionality, reduces duplication, and simplifies maintenance and updates.

  5. Team collaboration: Microfrontends enable decentralized team collaboration, allowing teams to work independently and be responsible for specific functionalities or features. This reduces bottlenecks and dependencies, improves productivity, and fosters a sense of ownership and autonomy among team members.

Microfrontends offer a modular and independent approach to frontend development, addressing the limitations of monolithic frontends. Because of their benefits, such as enabling parallel development, scalability, flexibility in technology choices, code reusability, and improved team collaboration, microfrontends provide a solution to building large and complex web applications efficiently and effectively.

Microfrontend architecture

Microfrontend architecture refers to the design and structure of a web application built using the microfrontend approach. The architecture encompasses various components and patterns enabling seamless integration and collaboration between these microfrontends.

  1. Backend for Frontend (BFF): The BFF pattern acts as an intermediary layer between the frontend and backend services. It handles communication, data retrieval, and orchestration of the microfrontends. The BFF pattern simplifies the frontends by providing a unified API for the microfrontends to interact with the backend services.

  2. Loader: The loader component is responsible for dynamically loading and unloading the microfrontends at runtime. It ensures that only the required microfrontends are loaded based on the user’s actions or the specific page being accessed. This improves performance and reduces initial loading time.

  3. Software Development Kits (SDKs): SDKs provide a set of tools, libraries, and guidelines for developing microfrontends. They facilitate consistent development practices, code sharing, and standardization across different teams or microfrontend modules. SDKs can include UI components, APIs, styling frameworks, and testing utilities.

  4. Components: Microfrontend architecture promotes the use of modular components within each microfrontend. Components are reusable and self-contained, allowing teams to develop and update them independently. Component-based development simplifies maintenance, promotes consistency, and encourages code reusability.

The following diagram shows the use of each component in a microfrontend setup:

Microfrontend Setup

(Microfrontend Setup)

Microfrontend frameworks and integration types

Microfrontend architecture allows for the integration of different frameworks and technologies to build modular and independent frontend components. Several frameworks and integration types are available to facilitate the development and integration of microfrontends.

The choice of microfrontend framework and integration type depends on factors such as project requirements, team expertise, and the need for interoperability. It is important to consider factors like performance, maintainability, and development experience when selecting the appropriate framework and integration approach. Ultimately, the goal is to achieve modular, independent, and seamlessly integrated microfrontends that collectively form a robust and scalable web application.

Here, we have a comparison chart for framework and integration types across some factors that can help decide which is suitable for a specific use case.

FeatureSingle-SPAWeb ComponentsModule FederationIframe Integration
Dependency managementManual configurationBuilt-inAutomaticMinimal
Framework agnosticWorks with multiple frameworksFramework agnosticWorks with multiple frameworksWorks with multiple frameworks
CommunicationCustom event systemProps and eventsShared dependencies and eventsCross-document messaging
Code isolationFully IsolatedPartial isolationPartial isolationFully Isolated
PerformanceLightweight and fastFast renderingOptimized loadingCan be slower depending on content
Reuse of componentsHighHighHighLow
Development effortModerateLowLowLow
Ecosystem maturityMatureMatureEmergingMature

While the frameworks and integration types help build a robust application, managing microfrontends involves implementing effective strategies and practices to handle the development and deployment. It also requires the maintenance of individual microfrontend modules within a larger application. Let us see, with the help of the following example, how we can efficiently manage microfrontends.

Getting started with Microfrontends

In this section, we will explore the deployment of a microfrontend application by creating a sample application. We’ll begin by configuring a dedicated repository for the microfrontends and setting up a CI/CD pipeline to automate the build and deployment process.

Microfrontend Application Deployment

(Microfrontend Application Deployment)

Prerequisites:

To successfully run the workflow we need to ensure the following:

  • AWS account: AWS account with appropriate permissions to create and manage AWS CloudFormation (CFN) stacks.

  • GitHub repository: We would use a dedicated GitHub repository where we will be using the below-mentioned structure to keep the code and YAML required for the CI/CD using GitHub actions.

  • GitHub secrets: In the GitHub repository, set up the required secrets to store the IAM user’s access credentials securely. (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, or ROLE_ARN for assume role operations)

  • Infrastructure: We will create the infrastructure (CFN stack consisting of Cloudfront and S3 Bucket) first using deploy-infrastructure.yaml and add all the required bucket policies and any required headers.

The repository setup will be of the following format. We will be using GitHub, GitHub Actions, and AWS to achieve the CI/CD configuration for deploying a microfrontend application.

.
├── 41-coke-overview.gif
├── .github
│   └── workflows
│       ├── ci-cd-pipeline.yml
│       └── deploy-infrastructure.yml
├── .gitignore
├── .infrastructure
│   └── infrastructure.yaml
├── LICENSE
├── microfrontend1
│   └── src
│       ├── App.js
│       ├── .babelrc
│       ├── components
│       │   └── Hello.js
│       ├── index.js
│       ├── package.json
│       ├── package-lock.json
│       ├── public
│       │   └── index.html
│       ├── react.js
│       ├── webpack.dev.js
│       └── webpack.prod.js
├── microfrontend2
│   └── src
│       ├── App.js
│       ├── .babelrc
│       ├── components
│       │   └── Hello.js
│       ├── index.js
│       ├── package.json
│       ├── package-lock.json
│       ├── public
│       │   └── index.html
│       ├── react.js
│       ├── webpack.dev.js
│       └── webpack.prod.js
├── README.md
└── test.md
  • .github/workflows/: This directory contains the GitHub Actions workflow file (ci-cd-pipeline.yml) for the CI/CD pipeline.

  • microfrontend1/ and microfrontend2/: These directories represent the individual microfrontends. Each directory contains the source code, public assets, and configuration files specific to that microfrontend.

  • src/: This directory holds the source code of each microfrontend, including the React components and application entry point.

  • public/: This directory contains the public assets for each microfrontend, such as the HTML template and other static files.

  • package.json: The package.json file for each microfrontend, which includes the dependencies and build scripts.

  • webpack.dev/prod.js: The webpack configuration file for each microfrontend, which handles the bundling and build process.

  • infrastructure.yaml: The CloudFormation template for creating the necessary AWS infrastructure.

For detailed structure, please check the GitHub repo of the sample microfrontend application.

Workflow of a Microfrontend Application

(Workflow of a Microfrontend Application)

1. Code

To create a microfrontend named microfrontend1, we will follow the recommended directory structure and create the necessary files. This will allow us to develop and manage microfrontend1 independently.

Create a directory for each microfrontend at the root of your project. For example: microfrontend1 microfrontend2

Let us create the setup for microfrontend 1 and 2 in the following structure:

├── microfrontend1
│   └── src
│       ├── App.js
│       ├── components
│       │   └── Hello.js
│       ├── index.js
│       ├── package.json
│       ├── package-lock.json
│       ├── public
│       │   └── index.html
│       ├── react.js
│       └── webpack.prod.js
├── microfrontend2
│   └── src
│       ├── App.js
│       ├── components
│       │   └── Hello.js
│       ├── index.js
│       ├── package.json
│       ├── package-lock.json
│       ├── public
│       │   └── index.html
│       ├── react.js
│       └── webpack.prod.js

An example source code for App.js, Hello.js, and webpack.prod.js for microfrontent1 can be found here.

In the above steps, we will add all the required code components to the repository. You can check the repository link mentioned above for detailed templates and code.

2. GitHub Actions workflow

The GitHub Actions workflow defined in the YAML file aims to automate the continuous integration and continuous deployment (CI/CD) pipeline for the microfrontend1. It is triggered on every push to the main branch.

name: Microfrontend CI/CD Pipeline
on:
  push:
    branches:
      - main
  workflow_run:
    workflows: ["CI/CD Pipeline"]
    paths:
      - '/infrastructure.yaml'

jobs:
  build-test-deploy-provision:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Set up Node.js
        uses: actions/setup-node@v2
        with:
          node-version: 14

      - name: Cache node_modules
        uses: actions/cache@v2
        with:
          path: microfrontend1/src/node_modules
          key: ${{ runner.os }}-node-${{ hashFiles('microfrontend1/src/package-lock.json') }}

      - name: Install dependencies with npm
        run: |
          cd microfrontend1/src
          npm install        

      - name: Run tests with npm
        run: |
          cd microfrontend1/src
          npm test

      - name: Build microfrontends
        working-directory: microfrontend1/src
        run: | 
          npm run build
          cp public/index.html dist/       

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: us-east-1

      - name: Deploy microfrontends to S3 buckets
        run: |
          cd microfrontend1/src

          aws s3 sync ./dist s3://microfrontend1-bucket --cache-control "max-age=3600, public"
          aws cloudfront create-invalidation --distribution-id YOUR-DISTRIBUTION-ID --paths "/*"

      - name: Check if infrastructure.yaml has changed
        id: check_changes
        run: |
          echo "Changes detected in infrastructure.yaml" \
            && echo "changed=true" >> "$GITHUB_OUTPUT" || echo "changed=false" >> "$GITHUB_OUTPUT"

      - name: Provision AWS infrastructure using CloudFormation
        if: steps.check_changes.outputs.changed == 'true'
        run: |
          aws cloudformation deploy --template-file infrastructure.yaml --stack-name your-stack-name --capabilities CAPABILITY_IAM

Following is the GitHub Actions snippet for the pipeline:

GitHub Actions snippet for the pipeline

3. CloudFormation Template (infrastructure.yaml)

The CloudFormation template (infrastructure.yaml) is used to provision the necessary infrastructure resources for hosting the microfrontends. In this example, we are creating AWS S3 buckets for hosting the static assets of microfrontend1, as well as an AWS CloudFront distribution for content delivery and caching.

Resources:
  Microfrontend1Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: microfrontend1-bucket


  Microfrontend2Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: microfrontend2-bucket


  CloudFrontDistribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Origins:
          - DomainName: !GetAtt Microfrontend1Bucket.RegionalDomainName
            Id: Microfrontend1Origin
            S3OriginConfig:
              OriginAccessIdentity: ''
          - DomainName: !GetAtt Microfrontend2Bucket.RegionalDomainName
            Id: Microfrontend2Origin
            S3OriginConfig:
              OriginAccessIdentity: ''
        Enabled: true
        DefaultRootObject: index.html
        DefaultCacheBehavior:
          ViewerProtocolPolicy: allow-all   # Add this line to set the viewer protocol policy
          AllowedMethods:
            - GET
            - HEAD
            - OPTIONS
          CachedMethods:
            - GET
            - HEAD
            - OPTIONS
          TargetOriginId: Microfrontend1Origin
          ForwardedValues:
            QueryString: false
            Cookies:
              Forward: none
        ViewerCertificate:
          CloudFrontDefaultCertificate: true

In this step, we have created infrastructure.yml which we will use to create/update our CloudFormation stack.

4. GitHub actions workflow to create and update only infra (deploy-infrastructure.yaml)

The deploy-infrastructure.yaml GitHub Actions workflow is designed to manage the creation and updates of infrastructure resources on the AWS cloud using AWS CloudFormation. This workflow can be triggered manually using the “workflow_dispatch” event.
deploy-infrastructure.yml

name: Deploy Infrastructure
on:
  workflow_dispatch:

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: us-east-1

      - name: Check if stack exists
        id: check_stack
        run: |
          stack_name="your-stack-name"
          if aws cloudformation describe-stacks --stack-name $stack_name; then
            status=$(aws cloudformation describe-stacks --stack-name $stack_name --query 'Stacks[0].StackStatus' --output text)
            if [ "$status" != "ROLLBACK_COMPLETE" ]; then
              echo "stack_exists=true" >> "$GITHUB_OUTPUT"
              echo "stack_status=${status}" >> "$GITHUB_OUTPUT"
            else
              echo "stack_exists=false" >> "$GITHUB_OUTPUT"
            fi
          else
            echo "stack_exists=false" >> "$GITHUB_OUTPUT"
          fi

      - name: Deploy infrastructure.yaml
        if: steps.check_stack.outputs.stack_exists == 'false'
        run: |
          aws cloudformation create-stack --template-body file://.infrastructure/infrastructure.yaml --stack-name your-stack-name --capabilities CAPABILITY_IAM

      - name: Update infrastructure.yaml
        if: steps.check_stack.outputs.stack_exists == 'true' && steps.check_stack.outputs.stack_status != 'ROLLBACK_COMPLETE'
        run: |
          aws cloudformation update-stack --template-body file://.infrastructure/infrastructure.yaml --stack-name your-stack-name --capabilities CAPABILITY_IAM

Following is the GitHub Actions snippet of the pipeline:

GitHub Actions snippet of the pipeline

Let us check if the microfrontends are running by accessing it in the browser.

Check if the microfrontends are running by accessing in browser

In this section, we successfully set up microfrontend1 and 2, creating a dedicated directory with the required components. We also configured a GitHub Actions workflow for CI/CD deployed microfrontend1 and 2 to AWS S3 and used CloudFront for efficient content delivery.

Microfrontends best practices

  1. Avoid horizontal teams: Instead of organizing teams based on frontend technologies, create vertical teams aligned with business capabilities. This allows each team to take ownership of a specific microfrontend and be responsible for its development, deployment, and maintenance.

  2. Isolate team code: Encourage teams to build microfrontends as independent and self-contained units. This isolation reduces dependencies and enables teams to work autonomously, making it easier to develop, test, and deploy their microfrontend without impacting others.

  3. Create a dependency management workflow: Establish a clear process for managing dependencies in microfrontends. Handle third-party dependencies centrally to avoid duplication and ensure consistent versions. Use package managers and dependency graphs to manage and update shared code and frameworks effectively.

  4. Embrace continuous integration and deployment: Implement CI/CD pipelines to automate the build, test, and deployment processes of microfrontends. This ensures that changes are tested thoroughly and released quickly, enabling rapid iteration and feedback.

  5. Use component libraries: Develop and maintain a shared library containing reusable UI components, styles, and utilities. This will promote consistency across microfrontends, reduce duplication, and simplify development efforts.

  6. Implement monitoring and error handling: Integrate monitoring tools and error tracking systems to monitor the performance and health of microfrontends. This enables teams to identify issues, track errors, and proactively address them for improved reliability and user experience.

  7. Documentation and communication: Encourage teams to document their microfrontends, including APIs, data flows, and integration points. Foster effective communication channels and knowledge-sharing practices to ensure a shared understanding of the system architecture and facilitate collaboration.

  8. Test at different levels: Implement a comprehensive testing strategy for microfrontends, including unit tests, integration tests, and end-to-end tests. Use testing frameworks like Jest for unit testing and tools like Cypress for end-to-end testing to ensure the quality and reliability of each microfrontend.

  9. Performance optimization considerations: Apply performance optimization techniques like code splitting, lazy loading, and caching to improve the loading speed and overall performance of microfrontends. Optimize asset sizes and reduce unnecessary requests to enhance the user experience.

By following these best practices, development teams can effectively manage and develop microfrontends while promoting collaboration, scalability, and maintainability within the architecture.

Challenges of microfrontends and ways to mitigate them

Microfrontend architecture brings many benefits, but it also comes with its own set of challenges. Here are some common challenges associated with microfrontends and strategies to mitigate them:

  1. Cross-cutting concerns: Handling cross-cutting concerns such as authentication, authorization, and shared services can be challenging in a microfrontend architecture. To mitigate this challenge, consider implementing a centralized authentication and authorization service that can be shared across microfrontends. Use token-based authentication and establish secure communication channels between microfrontends and backend services.

  2. Team collaboration issues: As multiple teams work on different microfrontends, ensuring effective collaboration and coordination can be challenging. To address this, establish clear communication channels, hold regular cross-team meetings, and encourage knowledge sharing. Maintain a shared documentation repository that includes architectural guidelines, API documentation, and best practices to promote consistent development approaches across teams.

  3. Performance issues: Microfrontends can introduce performance challenges due to increased network requests and potential duplication of resources. To mitigate this, leverage techniques like code splitting and lazy loading to optimize the loading speed of each microfrontend. Implement caching strategies at the client side and server side to reduce unnecessary requests and improve overall performance.

  4. Versioning and compatibility: Managing versioning and ensuring compatibility between microfrontends can be complex, especially when multiple teams are working on different release cycles. Implement a robust versioning strategy, use semantic versioning, and establish compatibility guidelines to facilitate smooth integration and minimize compatibility issues.

  5. Testing and quality assurance: Testing microfrontends individually and as a whole can be challenging. Implement a comprehensive testing strategy that includes unit tests, integration tests, and end-to-end tests. Leverage testing frameworks and tools that support microfrontend architectures, such as Jest and Cypress. Implement automated testing pipelines to ensure consistent and reliable test coverage across microfrontends.

  6. Scalability and deployment: Scaling microfrontends can pose challenges, especially when the application needs to handle high traffic volumes. Implement scalable deployment strategies, such as containerization, using tools like Docker and orchestration with Kubernetes. Use cloud platforms that support auto-scaling to handle increased demand efficiently.

  7. Monitoring and debugging: Monitoring performance and diagnosing issues in a distributed microfrontend architecture can be complex. Implement logging and monitoring solutions that provide visibility into each microfrontend’s performance and health. Utilize centralized logging and error-tracking tools to quickly identify and resolve issues.

By proactively addressing these challenges and implementing appropriate mitigation strategies, development teams can overcome the hurdles associated with microfrontend architecture and create scalable, maintainable, and robust applications.

Summary

In this blog post, we explored the concept of microfrontends, their benefits, architecture, frameworks, and challenges. We learned that microfrontends enable teams to work autonomously, making development, deployment, and maintenance more manageable. Using different frameworks and integration types allows for flexibility and interoperability within the architecture.

However, microfrontends also come with their challenges. Cross-cutting concerns, team collaboration issues, performance optimization, versioning, and testing can present hurdles. Nevertheless, these challenges can be mitigated through careful planning, effective communication, robust tooling, and best practices. In our next blog post, we will discuss some of the best practices and use cases.

In conclusion, moving towards microfrontends offers a compelling solution for building large-scale web applications by empowering development teams to work independently and create modular and scalable architectures. However, moving from a traditional monolithic architecture to a microfrontend could be challenging. In such a scenario, consider reaching out to microfrontend experts and they will securely migrate your application from monolithic to microfrontend architecture. InfraCloud also helps organizations migrate from traditional monolithic architecture to microfrontend.

We hope you found this post informative and engaging. For more posts like this one, subscribe to our weekly newsletter. We’d love to hear your thoughts on this post, so do start a conversation on LinkedIn with Suramya and Sonali.

References and further reading