DEV Community

Ibrahim Ragab
Ibrahim Ragab

Posted on

πŸš€ Unlocking the Power of Micro Frontends for Scalable Applications

Micro frontends have gained significant traction in recent years as a modern approach to building scalable and modular frontend architectures. They allow you to split a large application into smaller, independently developed, and deployable front-end microservices. As a lead front-end engineer, I’ve worked extensively with micro front-end architectures, harnessing their potential to streamline complex projects and enhance team productivity.

πŸ€” Why Micro Frontends?

Micro frontends align with the microservices architecture commonly used in backend development. They provide teams the flexibility to build features in isolation, resulting in several key benefits:

Decoupled Architecture: Micro frontends enable teams to work independently on distinct modules without impacting others, reducing integration challenges. This modularity results in a smoother workflow, allowing applications to scale effectively.

Independent Deployment: Each micro frontend can be developed, tested, and deployed independently, accelerating the deployment cycle and allowing incremental updates without downtime.

Tech Agnostic: Teams can use different frameworks or libraries for each micro frontend. However, it’s crucial to standardize certain practices, like linting and code quality checks, for a cohesive development environment.

πŸ› οΈ The Journey with Micro Frontends and Module Federation

In our projects, we manage an ecosystem of micro frontends, each focusing on a specific domain. Initially, we leveraged Module Federation in Webpack, which enables dynamically importing micro frontends at runtime. This approach allows us to encapsulate micro frontends and expose components or utilities across different modules without code duplication.

Module Federation ensures seamless integration between micro frontends while maintaining their independence. For example, when a user navigates from the fees management module to the category settings, the application dynamically loads the respective micro frontend, providing an uninterrupted and smooth user experience.

πŸ’Ύ Handling State and Side Effects with Redux Query

For managing state and handling side effects across our micro frontends, we rely exclusively on Redux Query. Instead of managing the state globally or using traditional state management tools, we use Redux Query to handle all data fetching, caching, and synchronization. This ensures that each micro frontend is data-driven, focusing on querying state only when needed.

🎯 Side Effects

We handle side effects in services and middleware rather than within the components. Using onQueryStarted from Redux Query allows us to initiate side effects like data fetching and real-time updates efficiently. This approach ensures that side effects are managed in a centralized and predictable way, promoting cleaner and more maintainable components.

By keeping all side effects in the services or middleware layers, we avoid the pitfalls of tightly coupling UI components with business logic, resulting in more reusable and modular code.

Example:

apiSlice.endpoints.getData.initiate.onQueryStarted(async (arg, { dispatch, queryFulfilled }) => {
  try {
    const { data } = await queryFulfilled;
    // Handle side effect
  } catch (error) {
    console.error('Error fetching data:', error);
  }
});
Enter fullscreen mode Exit fullscreen mode

🧩 Optimizing with a Data-Driven Approach

Our data-driven approach has enabled us to optimize components by removing unnecessary useEffect hooks, focusing solely on querying data when needed. For instance, when a user interacts with an enterprise or service provider dropdown, Redux Query efficiently manages the state changes and updates the relevant components. This not only simplifies our codebase but also improves performance and user experience.

πŸ—οΈ Key Components of Our Architecture

Micro Frontends: Our architecture comprises several micro frontends built using React, Redux Query, and TypeScript. Each micro frontend targets a specific domain, independently managed and deployed. This focus on independent modules allows us to enhance individual areas of the application without affecting others.

Shared Components: To promote consistency across micro frontends, we use a centralized library packaged with Rollup. Shared components, like forms or tables, are built once and reused, ensuring design consistency and reducing development effort.

Module Federation: With Webpack's Module Federation, we dynamically load and share components or utilities between micro frontends. This feature enables a seamless user experience across various parts of our application without requiring a full reload or significant overhead.

Communication: To facilitate communication between micro frontends, we rely on Redux Query's centralized approach to data management. This, combined with custom event emitters when necessary, ensures that data flows smoothly without creating tight coupling between modules.

Authentication: We’ve recently migrated to a new ACL (Access Control List) infrastructure, centralizing authentication across the main application and its micro frontends. This change has enhanced user experience and security, allowing for a unified access control system.

πŸ“ Best Practices and Lessons Learned

Utilize Redux Query for All Data Handling: By exclusively using Redux Query, we streamline state management and handle all side effects in services or middleware. This approach centralizes data fetching, caching, and state updates, resulting in optimized and maintainable code.

Automate Linting and Formatting: Maintaining code quality is crucial when working with multiple micro frontends. We standardized our projects with automated linting tools and integrated them into our CI/CD pipelines. πŸ›‘οΈ

Enforce PR Reviews: To ensure code quality and consistency, we established a policy where every pull request requires multiple reviewers. This practice has significantly reduced bugs and technical debt while promoting knowledge sharing among team members.

Experiment in Separate Branches: For experimental migrations, like transitioning from React 18 to the new React 19 beta, we conduct these changes in isolated branches. This method mitigates risk and ensures the main codebase remains stable during experimentation.

πŸš€ Future Directions

As we continue to refine our architecture, we're exploring further integration of our micro frontends into other platforms using module federation. This strategy will provide users with a seamless experience, allowing access to various services without leaving their main application environment.

🎯 Final Thoughts

Adopting micro frontends has transformed our development process, offering flexibility, scalability, and enhanced collaboration across teams. By combining micro frontends with Redux Query for centralized state management and side effect handling, we’ve streamlined our codebase and improved performance.

πŸ’¬ Have you experimented with micro frontends? Share your insights and experiences in the comments – let’s learn from each other!

Top comments (2)

Collapse
 
yousseftarek911 profile image
Youssef Tarek

very informative, maybe i will try in coming projects

Collapse
 
yuribenjamin profile image
Ibrahim Ragab

Thanks! Micro frontends are worth trying. Let me know if you need any tips