The author, Samir Lilienfeld, is a senior software developer at Bandwidth who works on the Bandwidth App for our global communications platform. Check out Bandwidth’s open positions if you’d like to work with Samir and the team. Join the BAND!
Currently, Bandwidth’s App is a complex monolith application to which multiple development teams contribute. However, all changes depend on a front-end UI Excellence team for approval and deployment.
In 2023, the UI Excellence team reviewed and approved 488 pull requests on the Bandwidth App repository—of which half were from teams outside of UI Excellence. This architecture caused a bottleneck that prevented teams from efficiently shipping new features and updates.
Enter micro frontends. Micro frontend architecture, or MFE, refers to a development strategy in which a web application’s front end is broken down into smaller, self-contained modules. Migrating our monolithic Bandwidth App codebase to a collection of MFEs would allow teams to own their respective UIs, giving them the autonomy to develop, test, and deploy independently of the UI Excellence team. Moreover, this infrastructure would allow for easier and quicker upgrades in the future, which is important for the application’s relevancy and security.
Transitioning the Bandwidth App to an MFE infrastructure posed significant challenges. The UI team’s initial hurdle was the absence of webpack’s Module Federation feature in the current webpack version. Module Federation is crucial for sharing build-time modules in MFE development. Additionally, some dependencies used by the App were incompatible with the new webpack version, necessitating updates. After thorough analysis, we discovered that these updated dependencies no longer supported our existing React version. We embarked on a year-long journey to upgrade from webpack 4 and React 17 to webpack 5 and React 18—here’s how we did it.
Upgrading delivered big benefits
Several reasons exist for upgrading dependencies, including security issues, the usage of new features, performance improvements, and the deprecation of outdated features.
In our case, upgrading to React 18 and webpack 5 forced us to update many minor dependencies. Although the main goal of this effort was to transition the Bandwidth App to an MFE infrastructure, these dependency updates had their own benefits.
Upgrading to React 18 was needed to make the Bandwidth App compatible with some dependencies that require features such as code splitting and other optimizations. This upgrade was also necessary since most third-party libraries no longer offer support for React 17.
In addition, React 18 introduced a new development-only check to Strict Mode. This check will automatically unmount and remount every component on the first mount while maintaining the original state on the second mount. It also ensures that the component is resilient enough to maintain its state even after being destroyed. Thanks to its performance improvements, this feature is planned to be standard in future releases, so this new feature is the first step towards that.
As mentioned before, the main reason behind the webpack upgrade was to gain access to Module Federation, which is only available in webpack 5 and is key to implementing MFE infrastructures.
Unrelated to MFEs or Module Federation—but also noteworthy—webpack 5 introduced improved code chunking. The code is split into smaller chunks, ensuring that when a file is updated, only the corresponding chunk is re-compiled. Other optimizations include having chunks that contain only CSS (instead of CSS files generating a JS file) and the ability to opt-in to persistent caching.
These benefits are a nice bonus of our upgrade efforts and they further justify the herculean effort we undertook to make it a reality.
How we avoided code conflicts
This upgrade was a long-term project that took about a year and required updates to over 1,700 files and 100,000 lines of code.
Keeping a parallel branch open for such a long time with this many file updates wasn’t an easy task. It is important to remember that while this work was ongoing, Bandwidth developers pushed code into the Bandwidth App’s main branch every week. In many cases, this new code introduced new pages and even significant changes to already existing pages.
It quickly became apparent that avoiding merge conflicts when pulling the latest changes from the main branch would be a challenge.
To accomplish this, our team relied on the Git rebase strategy instead of the merge strategy. Rebasing was crucial to ensure we kept a clean commit history, which reduces confusion and the likelihood that commits will get lost. This strategy also prevented the feature and main branches from diverging excessively.
We committed to rebasing our React 18 feature branch at least once a week to minimize code conflicts while updating and simplifying the tracking of changes. This strategy allowed us to keep our React 18 upgrade branch open for a long time without constant headaches.
Our QA process
We knew that an upgrade of this magnitude needed to be thoroughly tested before being released to our customers. This was accomplished in close collaboration with our Quality Assurance engineer and outside teams.
First, our QA engineer wrote a smoke testing template to help guide our QA process. Then, the App was divided into sections based on the team that contributed to that section. QA tickets were then created for each section. Each UI Excellence developer worked on these tickets and tested each section. This divide-and-conquer approach was key because it allowed us to meticulously test each section of the App.
After this internal smoke testing within the team, we reached out to additional teams to complete tests of their respective sections since they have more intimate knowledge of their own features.
With two rounds of reviews under our belt, we felt confident enough to proceed with the deployment.
A week before deployment, we put a code freeze on the repository and blocked merging to main. With everything QAed and tested already, we did not want to introduce changes that could potentially throw a monkey wrench into the deployment.
Deployment
We were hesitant to deploy the React 18 upgrade all at once because of the massive changes that were made. The upgrade changed 1,743 files, added 48,636 lines of code, and deleted 69,718 lines. Therefore, we started looking into blue/green deployments.
In a blue/green deployment strategy, there exist two identical environments–one with the current version and the other with the new version. At first, only the current version is available to users. Then, the new version is slowly deployed to a fraction of users. If issues are encountered, the new version can be rolled back. Otherwise, the new version is expanded to more users until it completely replaces the current one.
This strategy would minimize risk and accomplish our deployment goals. However, after some research, we found that our infrastructure would not allow for a doable implementation of a blue/green deployment strategy. As a result, we had to go forward with a “big bang” deployment. This is our normal deployment strategy: we simply deploy all the new files to an S3 bucket for our CloudFront distribution and wait for it to propagate. We determined that this was acceptable because–although we wanted to reduce risk even further–our methodical QA process, our new hotfix strategy, and our ability to monitor the App almost 24 hours a day (thanks to our global developer teams!) gave us more than enough assurance that our customers would not be impacted.
So, at 5 a.m. on the morning of the deployment, we crossed our fingers, held our breath, and hit the deploy button.
The deployment was successful. Thanks to our new hotfix procedure, there were only a handful of minor bugs that were found and fixed promptly.
What if something goes wrong?
In the past, a rollback was our most commonly used action for fixing bugs in production. Although effective, a rollback is the equivalent of “throwing the baby out with the bathwater.” The breaking change is removed, but so are all the other unrelated commits.
With a large deployment, we determined that rolling back was not an option. To address this issue, we established a new hotfix procedure:
- Check out the currently deployed tag. This new hotfix branch will be treated like main since it contains everything that is currently deployed.
- Make a branch off of this hotfix branch and make the changes there. Open a pull request against the hotfix branch and merge once the GitHub actions and reviews are done.
- The hotfix branch will automatically create a hotfix tag, which is the tag that will be deployed.
- Lastly, merge the hotfix branch back into the main, so that the hotfix change isn’t overwritten next time we deploy.
This new process allows us to fix an issue without undoing any unrelated changes.
Ready for MFEs
Developers often desire to upgrade applications but the business timing and support can be a balance. Often the benefits of upgrades do not immediately apply to all customers but are necessary if Bandwidth aims to deliver a quality product that delivers a best-in-class experience.
While these upgrades put us on a path toward an MFE infrastructure, they came with bonus benefits like improvements to React’s Strict Mode and webpack’s improved code chunking. Moreover, the upgrades were even lauded by the Infosec team, since the updates we made removed several high-priority security issues coming from outdated or legacy dependencies. This highlights another reason our team determined this upgrade was necessary: security. We want to always ensure the Bandwidth App stays relevant and up-to-date on all current standards.
As a result of this large deployment, we had a shiny new App running React 18 with all packages and dependencies updated to the latest versions.
Now that all of these updates are finished, our team is ready to tackle the next challenge: MFE infrastructure.