The evolution of the content library: An engineering story

Guest post by Flo Health: how they made a new format for organizing content, and iteratively developed a constructor-like platform for a new content format.
Published
March 30, 2023
Category

Insights

This is a guest post from the team at Flo, the world's number one app for women's health and well-being. Learn how the engineering team created a new format for organizing content, and iteratively developed a constructor-like platform for a new content format. This article first appeared on the Flo Health blog and is republished with permission.

graphic of user pressing a button

The first article about intent pages discusses the motivation and ideas behind creating a new format for organizing content. This article, on the other hand, focuses on the technical aspects of the project — specifically, how we iteratively developed a constructor-like platform for a new content format.

Prologue

At the start of the development of the new library (aka the Intent library), we had a few main nonfunctional requirements in mind:

  • Flexibility: It is extremely important for us to be able to launch dozens of experiments and new features quickly, without being limited by the release cadence of mobile clients. Ideally, adding a new UI element to the page should not require changes to mobile clients at all.

  • Usability for content creators: Each new page should be composed of modular elements. Content managers should be able to easily and quickly create and release content without any technical expertise or engineering involvement.

  • Interoperability: The Intent library is a significant revamp of an existing library. The plan was to gradually add new content elements on top of the old ones without changing the old library. The integration should be as seamless as possible: All existing content in the old library should be available in the new library so that users do not notice any changes in terms of content diversity after switching to the new library.

Decisions

Feature toggles and continuous delivery

During development, we heavily use feature toggles to help us develop complex features while maintaining a low cycle time. We can hide new functionality under a toggle and do small iterations by continuously integrating changes to “trunk” with further deployment to production.

The Evolution of the Content Library

The Intent library was no exception. The first thing we did was introduce a high-level feature toggle for the UI tab in the app so that we could control which tab to show to users (the new library or its predecessor). The toggle itself is not a simple Boolean value, but rather a kind of expression that allows us to enable the feature for a specific segment of users.

This new toggle will later be used not only for launching A/B experiments, but also for mitigating risks by gradually rolling out the feature, starting with a small group of users (like early adopters) and expanding to a wider audience. This approach is also known as ring deployment.

Back-end driven UI

We decided to use a back-end-driven UI approach for our new library. This approach is commonly used in Flo for A/B testing, quick delivery, and faster time-to-market. We have an article on our blog that explains more about this technique.

In short, we use our own framework, called UI Constructor, to describe UI elements using JSON on the back end. This allows our clients to render these JSON objects as native UI elements. With this approach, we can release UI changes multiple times a day, rather than having to wait for biweekly mobile client releases. The faster we can deliver changes, the sooner we receive feedback, and the better product we can build.

Example of server-generated UI elements

Example of server-generated UI elements.

Content management

At Flo, we use Contentful as our content management platform. It helps us solve a number of problems, such as:

  • Decoupling development and content management teams. Our engineering teams focus on creating and supporting the platform built on top of Contentful (e.g., content models, validation, and evaluation of personalization rules), while content managers are responsible for the content itself (e.g., localization, personalization rules, and content composition).

  • Ensuring content quality and consistency through strict validation rules. Contentful’s preview capabilities and inbox validation allow us to catch most content issues before they reach production. In addition, Flo created custom UI components using the Contentful App Framework, which helps us validate our content personalization rules and understand the target audience for a specific piece of content before releasing it to production.

  • Fast delivery. In combination with a back-end-driven UI approach, we can build a flexible platform that enables content managers to assemble a complex and unique user interface using blocks right from Contentful without changing anything on the back end or client.

Since the old library was also built on top of Contentful, we could easily reuse its existing content while introducing new content types seamlessly.

Prototype

In the beginning, we needed to prove that the concept and ideas behind intent pages are valuable to users, so we didn’t want to invest a lot of effort into developing a full-fledged solution that covered all aspects, from content management to rich user experience. Instead, we decided to start with one intent page and a few new UI elements.

Given the small amount of new content for the prototype, we didn’t invest in integration with the content management system or think about the content model in detail. At that point, no one knew how promising the initial idea was. Instead, we started by keeping the content of intent pages along with the back-end code. Thanks to the back-end-driven UI, we are not bound by strict contracts with clients, so in most cases, we can easily reshape our content models anytime without affecting our clients.

Skipping all this heavy lifting allowed us to launch the first experiment blazingly fast. The downside was that we incurred a small debt that needed to be fixed later. At Flo, we call this kind of debt product debt. Hit this link to learn how we deal with tech debt at scale.

Evolution

After conducting multiple A/B tests, we realized that we were spending a lot of effort just keeping our existing in-memory pages up to date. Since we were keeping pages along with the back-end code, content managers couldn’t update the content on their own and had to frequently ask developers to make changes. This reduced our development capacity.

Given the positive outcome of the experiments, we decided to temporarily stop developing new product features and focus on addressing our product debt by investing more in content management. This freed up our development capacity and made it possible for content managers to release content without involving the engineering team.

We came up with the following content model:

We came up with the following content model

We have a central entity, or content type, representing a single page. This type aggregates all the content pieces (sections) and metadata required for display in the app. Since sections are defined as references, it’s possible to change their order using drag-and-drop. This makes content sections reusable across multiple intent pages.

Example of how content managers can combine different content blocks

Example of how content managers can combine different content blocks.

It’s important to note that at Flo, we have multiple product domains, and each domain has its own entities in Contentful. These entities are managed by independent services owned by separate teams. Because of the nature of intent pages, our central entity must act as an aggregator that can reference entities from multiple domains. Cross-domain integration can be implemented in various ways, but for low coupling, we chose integration through API over direct references through Contentful. We considered this a good trade-off between maintainability and performance.

One of the most interesting features of this model is its dynamic personalization rules, which involve assigning predicate fields to specific entities. These predicates allow you to target specific groups of users, such as those who track their menstrual cycles or those who are trying to get pregnant. With this powerful mechanism, content managers can create adaptable, personalized content.

At the same time, despite being very flexible, the current content model has disadvantages as well. Since all content blocks are represented as separate content types, it can sometimes be challenging to create and assemble all the pieces into the final page.

Results and facts

  • Intent pages have already been released to a wide audience, and this new format really captures users’ attention.

  • The platform built for intent pages has also garnered much interest among product teams within Flo. Some teams see intent pages as a foundation for their new features.

  • We conducted dozens of A/B tests throughout the journey. While not all of the experiments were successful, that’s just part of the process when it comes to being data driven. We learn from our failures and keep trying new things.

Next steps

We plan to look at the Compose app to make the content creation process even more convenient. The key feature is that it allows editing all child entries in one place instead of going through dozens of pages and editing blocks one by one. This should provide a seamless experience during content creation and should make our content managers happier.

Also, we want to invest more into making the content model even more flexible, to be able to easily perform A/B tests on a content level. For instance, we could experiment with different structures of the same intent page: different orders of content blocks, texts, etc.

Start building

Use your favorite tech stack, language, and framework of your choice.

About the author

Don't miss the latest

Get updates in your inbox
Discover how to build better digital experiences with Contentful.
add-circle arrow-right remove style-two-pin-marker subtract-circle remove