Buzzblocks is a way to share different components and behaviors across the BuzzFeed website. Before Buzzblocks, the team focused on the consumer webapp (buzzfeed.com) was only using BuzzFeed's pattern library to build cohesive experiences. There were no tools in place to share components. When the same user need presented itself in multiple places, it was easy to duplicate code, but doing so presented its own set of problems. We decided to spend the better half of 2017 tackling that problem, which had been lurking underneath the surface since we introduced the idea of microservices in 2016.
At a very high level, there were a few pretty solid reasons to create a shared component library:
Only our highest trafficked pages were in the new stack; our older pages were still considered legacy. Navigating from the homepage to an older page, such as BuzzFeed Animals, often meant experiencing this jarring jump between pages.
Though we knew the future was uncertain, we set goals in place that were attainable yet flexible:
The project started out like any other ambiguous systems project: with a ton of questions. We documented our pain points, researched how other companies handled similar challenges and wrote up some loose proposals for how to get started.
Early notes on categorizing, identifying, and building components
Working through the product and design implications for the component library was an essential part of this project, but for the sake of brevity, I’ll save that for another case study. The design and engineering team already had a consensus around which components to move into the system; in most cases, we were even able to identify props. We also addressed pieces of “legacy” UX that we could remove in place of newer components, and worked through components still on the table. We used Basecamp, Slack, and in-person critiques for these discussions.
Components like the story package were well-defined from a product and design perspective and ready to move into the system immediately
A few things we kept in mind:
The Developer Experience
It was necessary to focus on the developer experience at this stage of the project. The team had a long-term vision of making opinionated improvements, at scale, across the consumer website. This included addressing accessibility issues, implementing typographic choices, and even introducing the ability to prototype with dynamic components.
All of these ideas seemed unattainable because we didn't have our core components in a stable place. Implementing a shared system would be the first step, and it was necessary to design a sustainable and easy workflow to maintain that system.
Due to the nature of the project, we wanted to be as transparent as possible. We started a Slack channel with weekly pinned updates, roped in engineers from other teams into PR’s and Issues, and posted our progress on Basecamp. Having avenues for feedback and collaboration was essential since ultimately the success of the library depended on our teams adopting it in their day-to-day work.
Pinning weekly Slack updates was the easiest way to be transparent early on
We decided to learn by building a "pilot" component in the library. The BuzzFeed header was already top of mind as it was difficult to keep in sync across the site. When someone made a change to the header, they needed to update it in every single service. This meant a lot of unintentional inconsistencies slipped through the cracks.
At the same time, the header had accrued design debt over the years but didn’t fall on any specific product teams’ plate. It made sense for us to tackle those issues with the technical refactor.
Moving the header into a shared component library meant killing two birds with one stone: cleaning up the header IA while refactoring the code
Working with a pilot component to suss out difficulties proved extremely useful. For example, we initially wrote a combination of BEM (component-specific) styling along with Solid (atomic styling) to style each component variation. We learned soon after that it was very, very confusing to read and even more confusing to edit.
BEM for the win in this scenario. Atomic styling made sense outside of components, but we wanted isolated styling and decided to utilize Solid’s variables inside scoped BEM classes instead. With the help of a few volunteers, we wrote our first draft of contributing and developing documentation based on how we built and shipped the shared header.
We also worked quickly to get the first iteration of the component gallery up and running. We put the work in Basecamp and held discussions over Slack and GitHub.
We documented some hiccups we ran into along the way when building the header, such as the benefit of breaking up templates, when to fork vs. modify, and (as mentioned above) the value of sticking with BEM conventions.
It was really important to talk to engineers who were beginning to experiment with the library to get their feedback, as well. A few initial improvements that came out of that feedback:
While we began implementing a lot of these improvements, we noticed a trend happening. While we made it quite easy for people to contribute new components to the library, we didn’t make it easy to know when and if they should add new components to the library.
We also noticed that components added were not well-defined in the gallery and were often missing the proper context for use. We hadn’t enforced any rules or guidelines around documenting components. We wrongfully assumed our (well-intentioned) engineers would be led by our not-so-obvious examples. The low barrier to contribute to Buzzblocks led to inevitable bugs and discrepancies in production.
A component missing clear documentation and context.
We spent a lot of time discussing the issues to get to the root of the problem and ultimately decided to (at least initially) take more responsibility for the individual components themselves, rather than just maintaining the library and framework.
Taking more responsibility for the components would allow us to learn more about why these issues were occurring, and how we could avoid them in the future. It would also give us an opportunity to set a better precedent so that folks contributing to the gallery in the future had strong examples and best practices to lean on.
Fast forward and after a couple weeks of investigation, refactoring, and discussion, we uncovered a few common themes to back-up the challenges we were facing…
False positives: Components weren’t always thoroughly represented in the gallery’s sandbox and prototyping environment. This was sometimes a result of “hidden” styling that existed only in a consumer service, and not in Buzzblocks. False positives happened a lot when migrating components to Buzzblocks from a service. Moving things from one place to another resulted in dependencies accidentally getting left behind.
Unknown Unknowns: With the multiplication of services and pages, it was hard to know where each instance of a specific component lived. It became difficult to run regression tests for all the different configurations of a component across multiple services.
For example, a straightforward update to our story component turned out to have broken the entire feed on the BuzzFeed Shopping page, due to hidden service-specific dependencies. Who’da thunk it.
An actual visualization of Cascading Style Sheets
The most straightforward and effective way we could mitigate these risk factors were to build isolated components. We decided that we were no longer going to share SCSS or JS across multiple components unless they share enough likeness. We took this route because self-contained and self-reliant components (from both a stylistic and engineering standpoint) are more predictable, stable, and easy to test. This also meant that we would stop using components inside of other components. The framework didn’t allow for composability in the ways that we needed to feel comfortable importing partials.
Outcomes > Philosophy Ideally, we wanted to build a system that could support composability and encapsulation. However, the reality of the situation was that, due to lack of encapsulation options with our frontend stack, isolated components were the safest bet. This outcome was disappointing but allowed us to move forward and create a valuable system for our existing framework.
A house is only as strong as its foundation We bent our frontend framework so far it broke. It wasn’t meant to host components the way that we were previously integrating them. The components weren’t encapsulated by nature, but we could isolate them to the best of our ability to make them more manageable and less risk-prone.
Know when to take ownershipTaking on the components themselves built up confidence and helped to inform documentation we felt confident about sharing. Following through and making sure the documentation still made sense in practice was key.
It’s never finishedThere are still quite a few things that aren’t “there” yet. We’re not confident saying this is the best library that it could be. What we are aligned on is the things we need to do to make it better.
After trying, and trying again, we reworked our component definition to reflect the learnings and best practices. A component in Buzzblocks should be:
We also reworked the rest of the documentation; this time with loads more confidence than before. We also added a "checklist" for developers to sanity check whether or not a component makes sense to live within the Buzzblocks library. After we felt confident enough with the changes, we (once again) encouraged contributions from the rest of the engineering team.
An engineer on the team made an easy-to-follow checklist for contributing components.
At the end of the day, Buzzblocks components made up a significant chunk of our primary consumption experience:
I’m also extremely proud of the team for the huge collaborative effort to move all of BuzzFeed’s legacy pages into the new stack, using primarily Buzzblocks components. This includes pages that had long been considered “design debt”, such as our author pages, older category pages, and our search page.
I can say with confidence that this would have been a much more stressful and lengthy undertaking if it hadn’t been for Buzzblocks.
Using Buzzblocks meant we could offer a responsive experience to pages that previously didn't exist on mobile, such as our search page.
As new brands with different needs continued to branch out of BuzzFeed, Buzzblocks became a strong blueprint for how we tackle libraries across the board. I’m curious to see a world where those unique component libraries have a unifying factor, which right now is the architecture and structure of the library.
Aside from the front-end framework limitations, we identified several opportunities for improvements in the future. For starters, per-component versioning would solve a lot of pain points when introducing changes to the library. Additionally, both engineers and designers wanted more context around a component's usage (which services it lives in and which version is the latest). Regarding design best-practices, we barely scratched the surface of what a component library can do to influence a product's ecosystem.
After we set the new standards and place and started using them in practice, we felt light-years more confident about the code we (and the teams contributing) were shipping.
Buzzblocks ultimately became the responsibility of the feature teams that were implementing it. That said, there continues to be a working group of core “maintainers” who are available to answer questions and make improvements to the framework and library as they come up.
I am genuinely impressed by the engineers and designers who embraced this library and new workflow with so much enthusiasm and understanding, and am excited to see the impact it has on future products.
Product Design and Development
Ian Feather (Engineering Manager), Jack Reid (Engineer), Artyom Neustroev (Engineer), with input from BuzzFeed's cross-functional web teams