Lean and flexible content with Contentful

If you’ve ever worked with a content management system, you’re probably quite familiar with the problem that once you publish your content on one platform (e.g. WordPress) you can’t easily use it anywhere else!

And once you want to publish your content on other platforms as well, you need to figure a whole multitude of things like: how do I provide this data via a content API? Can I deliver different image formats? And how can I break up that big text blob into smaller pieces?

We’ve written previously in the Logbook about some of the challenges of building a mobile app in React Native for our recent project with Prepd. This article will give you another peek behind the curtain at how we tried to make a great user experience for content editors and app users.

What’s COPE?

There is a paradigm that has started to challenge the idea of monolithic content systems a long time ago, called COPE, meaning “Create once, publish everywhere”. It gave rise to a so-called headless and decoupled form of CMS that allows for more flexible data structures, better scalability, and an out-of-the-box content API that allows you to pull in your data from anywhere.

In our recent iOS/Android project for our client Prepd, we had to provide data to two different mobile apps and a potential future web app, so it was clear to us that a traditional CMS like WordPress wouldn’t cut it.

Having worked with COPE-like platforms before, we knew that a far better solution would be to find the right kind of CMS that would allow us to leverage an API-driven workflow: one where we could provide all our data to the apps via a flexible content API. This is what we call an API-driven approach.

Why Contentful?

After evaluating our options, we opted for Contentful, which (unlike the WordPress Rest API) was quite mature at that point, and provided all the functionality we needed to go ahead:

  • A RESTful API
  • Access for different content editors
  • An intuitive user interface that wasn’t too technical for users
  • Flexible field types: a text type, an image type, select fields etc.
  • The ability to write instructions for each field to assist editors with content input

Additionally, it would give us a lot of flexibility for structuring our content:

  • We created different “Content Types”: each of these could contain a different set of fields (like text fields, images, but also reference other content objects)
  • For each “Content Field”, we were able to define validation mechanisms and fine-tune their appearance, such as:
    • It being a short or long text, or a decimal number
    • It being a media item, e.g. an image or video
    • For references, we could define which Content Type to link to
Screenshot of the Content Model configuration in Contentful
The data model of our recipe content type in Contentful, where you can see different field types in action


Screenshot of a recipe being filled with data in Contentful
An example recipe: this is where the the predefined fields get filled with real content.


Example of some JSON output of the recipe data for Cashew recipe
This is a (shortened) extract of the universally consumable JSON response of the Cashew recipe.

Using the data in our app

You’re now really craving for some cashew nuts, aren’t you?

The next challenge was pulling that data from the API and integrating it with our app. Initially we thought that we’d just make API requests from the app whenever a user interacts with it. That way, she would always have the most recent data available on her phone.

The caveat however would be that we’d quickly reach our API quota limit, and that the app would first have to make a network request before any data would show up, something that we realised would take a few seconds and significantly affect the perceived app performance.

Doing it the static way

The solution (at least for our initial app launch) was to pull the data from the API every time we created a new build (via Bitrise), and then including all the JSON files and image data in the app bundle.

Doesn’t this make the app significantly bigger? Yes, by about 15MB. But if you look at the average app size on your phone, you’ll realise that anything below 50MB is still quite reasonable (particularly given this app’s target market) and especially since this data only has to be downloaded once.

On the other hand, we had to make the following trade-off:

  • Either we could opt for scalability and somehow create a smart mechanism that would download and cache the data on the user’s phone. This would have increased development time by a few days, if not weeks in the long run and reduced the time we had available to build other features for the app. It would also be a premature optimisation, given what the app needed to do for its initial launch.
  • Or, we could just be quite radical and say: we’ll put all data in our app and defer our scalability concerns to a later point in time, when managing content this way starts to become limiting. But we’ll not bang our heads against the wall for now, and will surely find a solution once we get to that point.

To this day, the app is still handling the volume of content well and we’re glad we went down this simpler route. And so far, not a single user has reported that downloading the app has taken too long.

Using the Image API

Another neat thing about Contentful is that it provides a fully-fledged image API that allows you to resize, crop and modify images on the fly. Since we were concerned about app size, we needed the right image size for our mobile apps, which would just be good enough for our biggest screen resolution.

The original recipe images were huge, but content editors weren’t able to easily edit and resize them before uploading to Contentful, so we had to automate the resizing process. Since we weren’t even sure of the exact dimensions needed at that point (whether it would be used on iPhone 6, 7, Android devices, or even iPad), and since the image usage might change in the future (with the iPad Pro, for example), we just let content editors upload the highest quality source image to Contentful.

Using the Image API, we could then define dynamically which size the image should be delivered at in the app, by simply adding that parameter to the request URL. In the beginning, we went for 300x300px images, knowing that changing this size would just be a matter of changing the corresponding parameter in our download script, like so:

How we pulled and stored API data in our app

Here’s a script that exemplifies how we were downloading the API data from Contentful, and storing that (alongside the referenced images) in the app folder, which would later be compiled into the iOS and Android app bundle:

// initialise Contentful API

const client = Contentful.createClient({
  space: process.env.CONTENTFUL_SPACE_ID,
  accessToken: process.env.CONTENTFUL_ACCESS_TOKEN

// pull data from Contentful, and save it in a local JSON file

export async function fetchToJson(type, options = {}) {
  let entries = await client.getEntries({content_type: type, limit: 1000});
  fs.writeFileSync(`app/assets/data/${type}.json`, JSON.stringify(newEntries));

Are content APIs the future?

Midway through the project, we also realised that it would be hugely valuable to have all the recipes available on the web so that they could be shared by users. Thanks to our decision to host all of our data in Contentful, we managed to build a very simple ‘web app’ in just a day or two to achieve this.

After working with Contentful, we realised just how powerful this platform-agnostic approach can be and have adopted this approach for other projects as well.

We’re not solely used Contentful: in some cases, it’s too complex (and expensive) for the needs of the project and a simple solution like DatoCMS has been a great fit.

One very interesting feature of Contentful, however, is the ability to write custom extensions, which allow you to interact programmatically with content fields. In this way, you can pull data from other third party services, update your data with the click of a button, and automate large chunks of your content workflow. In a follow-up blog post we’ll talk about how we used this feature to automate the generation of nutritional data for our recipes. Stay tuned..!