The definition of what constitutes a “micro frontend” perhaps hasn’t yet reached consensus. The smart folks at DAZN consider it to be a series of full pages managed by a client-side orchestrator. Other approaches, such as OpenComponents, compose single pages out of multiple micro frontends.
BuzzFeed’s use case fits somewhere in between the two. I wouldn’t say we have a micro frontend architecture; however, we do leverage them for a few parts of the page. We consider something to be a micro frontend if the API returns fully rendered html (and assets) but not an <html>
or <body>
element.
We have three micro frontends: the header component, the post content, and our interactive embeds. Each of these adopted the micro frontend approach because they presented real and distinct business problems.
Micro Frontend #1: The Header
Why? Component Distribution
This is the buzzfeed.com header. It has a light layer of configuration as well as a reasonable amount of code behind it: certainly enough that it merits an abstraction as opposed to duplicating it in all of our services.
Originally, we made this abstraction and extracted it into an npm package, which services imported as part of their build process. This allowed us to remove duplication as well as have the service bundle the header as part of its own build process (meaning we could easily deduplicate common code and libraries).
With only two or three services, this technique works really well, but we have more than ten rendering services backing buzzfeed.com. This meant that every time we wanted to make a change to the header we had to make the following changes more than 10 times:
- Update the code in the header
- Make a Pull Request
- Merge and publish to npm
- Update the service package.json
- Make a Pull Request
- Merge and Deploy the service
This became extremely time consuming and led to teams avoiding header changes because of it. Sure, there are ways in which we could have improved this workflow (e.g. using loose semver and just rebuilding the service, automating the update and creation of service PRs) but these still felt like the wrong approach. By moving to a micro frontend pattern, we’re now able to distribute the header instantly to all services and the workflow to update it on all of buzzfeed.com is now:
- Update the code in the header
- Make a Pull Request
- Deploy the header
Micro Frontend #2: Post Content (or as we call it: The Subbuzzes)
Why? To maintain a contract with the CMS
We have a few different “destinations” (e.g., BuzzFeed and BuzzFeed News) for our content yet each one is powered by a single CMS. Each destination is its own service (or multiple services) which connects to our content APIs. This means that we have the ability to render the same content in multiple destinations; however, in practice we choose not to.
This also means that we have to maintain a contract between the CMS / Content APIs and the rendering services. To illustrate this it’s easier to focus on an example.
When an editor wants to add an image to the page, they select the image “subbuzz” in the CMS and upload it. They then have the option to add extensions to that image. One such extension is the ability to mark the image as showing Graphic Content. The intention of adding this extension is that the image would be blurred out and the user would have to opt-in to see it (this is particularly important with sensitive news content). As far as the CMS cares though, all this means is a boolean value stored against an image. Because the CMS depends on the rendering services to add a blurred overlay we end up with an implicit coupling between the two. If a destination failed to support this feature then users would be exposed to graphic content, and we would have failed to uphold the editors’ intentions.
So what does this have to do with Micro Frontends?
We could choose to abstract these subbuzz templates into an npm package and share them across the destinations; however, when we change support for something in the CMS we need the rendering services to be able to reflect this immediately. The CMS is deployed in an un-versioned state, and the content APIs only expose major version numbers. Coupling these with npm packages using semver and deployed via a package would make it harder for them to stay in sync. By moving the subbuzzes behind an HTTP API, we can update the rendering-cms contract across all destinations immediately and guarantee that each destination supports the latest CMS features.
Micro Frontend #3: Embeds (Buzz Format Platform)
Why? Independence from the platform
Maybe the most clear use case for Micro Frontends: the Embed. We host a ton of embeds (Instagram, Twitter, etc.), including first-party embeds. We call these BFPs which stands for Buzz Format Platform, and they can be anything from a newsletter signup to a heavily reusable quiz format or a bespoke format supporting an investigative story.
The entry point for an embed is typically an iframe or a script element, so it doesn’t really qualify as Micro Frontends themselves. We break that mold (where possible) by rendering them server-side and including the returned DOM directly in the page. We do this so that we can render the embeds in distributed formats (like our BuzzFeed Mobile App or Facebook Instant Articles) as well as expose the content to search engine crawlers.
BFP provides independence from the platform and gives engineers the feeling of working on a small component without having to consider the wider BuzzFeed ecosystem. This feeling is one we always try to get to when creating developer environments and Micro Frontends certainly provide that opportunity.
The trade-offs
A micro frontend architecture can give you a great developer experience and a lot of flexibility, but they don’t come for free. You trade them off against:
Larger client-side assets or tougher orchestration
We compose our micro frontends in the browser which means there’s no singular build process that can optimize and deduplicate shared dependencies. To achieve this at the browser level, you need to code-split all dependencies and make sure you use the same versions — or build in an orchestration layer.
Higher risk when releasing updates
Just as we’re able to distribute new changes instantly across many services, we’re also able to distribute bugs and errors. These errors also surface at runtime rather than at build time or in CI. We use this heightened risk as an opportunity to focus more on testing and ensuring that the component contract is maintained.
There has also been criticism that micro frontends make it tougher to have a cohesive UX, but this is not something we’ve experienced. All of these micro frontends inherit design patterns and smaller components via shared packages.
Overall the micro frontend pattern has worked well for BuzzFeed Tech in these use cases and has been well tested over the last one to two years. There is definitely an inflection point where having many more of them would require more work to offset the first trade-off but, we don’t think we’re there yet and don’t anticipate being there any time soon — abstracting components to shared packages work well for the majority of our cases. Where it doesn’t, it’s nice to have another architectural pattern to reach for.
Top comments (2)
Thanks for sharing!
Awesome post! Thanks for sharing your thoughts, it's cool to see what goes on behind the curtain on sites like yours.