We reduced the load time of a popular hospitality app by breaking its monolithic frontend into micro frontends, each serving only the relevant module (such as housekeeping, reservations, or configurations) to users. It performs reliably, even on slow connections, and is now easier to maintain.

Why the Monolith Had to Go

Our client’s hospitality platform had over 20 modules, all bundled into a single Ember codebase on the frontend. Loading the app meant delivering a giant JavaScript bundle, which degraded performance and frustrated users.

Feature development and bug fixes were risky and time-consuming. With everything in a single repository, developers couldn’t isolate changes or deploy modules independently. Release management was messy, and a clean deployment was impossible to achieve.

UI inconsistency was another concern as developers were working with separate CSS styles across modules. Without a unified styling approach, the look and feel varied between modules, leading to a non-uniform user experience.

Also, in a world increasingly powered by React, the Ember-based monolith felt outdated. Re-architecting the system with a modern tech stack became the only viable path forward.

The Shift to Micro Frontends

We evaluated several frontend strategies to support both our existing Ember codebase and the future shift to React.

One of our early ideas was to separate modules and extract reusable code into shared packages within a monorepo using Module Federation. However, Module Federation requires all modules to use Webpack and doesn’t easily support cross-framework integration, which made it less suitable for our gradual migration plan involving multiple frontend technologies.

After evaluating several approaches, we ultimately chose to modularize the application using Single-SPA , a micro-frontend framework that allowed us to:

  • Run Ember and React together in the same application
  • Load only the necessary module based on the route, improving performance
  • Share global state across modules when needed

This enabled a smooth, incremental migration to React without disrupting the entire system.

Reducing Redundancy with Shared Packages

To support reuse across modules, we also planned a set of shared packages to serve as the application’s foundation.

  • Web Components with Stencil - Using Stencil.js, we created framework-agnostic UI components (buttons, modals, form inputs, etc.) that could be reused across all modules regardless of whether they were built in React, Angular, or Ember.
  • Infrastructure Components - We built reusable layout and data-bound components such as sidebars, toasts, data grids, and forms with generic event handlers. This again helped in reducing the effort of creating the shared components again and again.
  • Utility Libraries (library-utils) - Shared business logic, formatting functions, and constants were moved into this package. This approach helped enforce DRY (Don't Repeat Yourself) and SOLID principles, leading to cleaner, more maintainable, and scalable frontend code by reducing redundancy and improving modularity.
  • API Layer (library-api) - All API integrations and HTTP logic were consolidated in a shared API library. simplifying consumption and maintenance across modules.
  • Code Quality Library - To enforce consistency in coding standards and best practices across teams, we established a shared code quality package. After its introduction, it became mandatory for all modules to install this package and validate their code quality against it.

With these library packages, we were able to reduce the bundle sizes from megabytes to kilobytes.

Here's a simplified architectural block diagram that illustrates our micro frontend setup:

Architecture diagram of a container app integrating multiple micro frontends (React and Ember) with shared libraries for API, utilities, UI components, and global state.

Implementation Approach

We adopted a multi-pronged implementation strategy. The team was split into smaller units, each led by an owner. A dedicated core team was tasked with setting up the micro-frontend container and building shared packages.

Work across the remaining teams was organized into three streams and was executed over multiple sprints:

  • Developing support for micro-frontend modules
  • Migrating legacy Ember code to React
  • Extracting shared code into standalone libraries

Handling Components

Early on, we ran into issues with the web components. Because components were tested only within individual modules, they often behaved inconsistently across the app. A component that worked in one module would break or require customization in another. To address this, we revised the workflow: components were first developed and passed to QA for validation. Only QA-approved components were published to the shared library and consumed by downstream modules.

Module Migrations

One of the most complex modules to migrate was Reservation, given its rich functionality and extensive test coverage. Initial efforts were hindered by limited understanding of the business logic, resulting in gaps in migrated features. We addressed this by involving product owners early, reviewing existing workflows in detail, and defining user stories with explicit acceptance criteria. This significantly reduced errors and improved migration quality.

Shared Tools for Dynamic Configuration

Another major focus area was the configuration module, which manages settings such as currency formats, report templates, and other entity-specific configurations. To support this, we built a shared form builder and a reusable data grid component, enabling dynamic configuration for roughly 150 entities across all modules. This removed the need to build and maintain separate form fields and grid implementations for each entity.

Iterative Execution

Our approach was iterative. Feature development and migration tasks progressed in parallel, allowing us to maintain momentum without blocking business needs. Coordination between teams ensured that shared components evolved alongside module-level migrations.

Release Management

We followed a versioned release process with defined checkpoints each week. A release candidate for shared packages was issued ahead of module deployments, which were handled independently by each team. Teams were expected to adopt, test, and provide feedback within a fixed timeframe. Stable versions were released mid-week, followed by deployments.

For any hotfixes, the respective modules would be deployed with a minor or patch version update. The CI/CD pipelines for package creation and deployment were set up to automate this entire release process.

By the end of the first year, over 50% of the codebase was migrated to React using the micro-frontend framework. The entire migration was completed within two years.

Impact of Micro Frontends on Performance and Delivery

The shift to micro frontends transformed how the platform performed and how teams worked. Load times plummeted, development accelerated, and the experience became smoother for both users and engineers.

  • Load Time Dropped from 3 Minutes to Under 3 Seconds - Users now load only the code needed for the module they access. The app is faster, lighter, and performs well even on slow connections.
  • Improved Ownership - Where developers once struggled with merge conflicts and slow releases, they now own their modules end-to-end, coding, testing, and deploying independently. This shift has reduced delays.
  • Visual Consistency Across All Modules - Stencil-based web components established a common design system, ensuring UI/UX consistency across frameworks.
  • Faster, Independent Releases - Each micro frontend has its own release cycle. Teams ship features when they are ready.
  • Higher Test Coverage - Smaller scopes made testing easier and more reliable, improving CI/CD feedback loops and platform stability.

Conclusion

Breaking up a monolith into micro frontends is a complex undertaking. It requires tight coordination across teams, careful handling of shared dependencies, and safeguards to prevent regressions. We managed this through detailed planning, a phased rollout strategy, and strong leadership. The outcomes translated into measurable gains for both our client’s operations and end-user experience.

No Image
Senior Principal Engineer