Markus Bergh

Deliver your content with Contentful

Deliver your content with Contentful

Using static files in JSON format to serve my portfolio with content was the normal procedure for me for a long time. When adding a case, or updating any content I had to do a deployment with a new version for my visitors. This was something I needed to change to be able to move forward faster.

By making the dynamic content more easily editable, my portfolio would also become more alive than it currently was. And since I was already handling a JSON structure in the application, it would easily swap out the static files I was using with something else.

I found out about the JAMstack, and the idea of a headless CMS that decouples the presentational layer from the content administration layer. It makes it easier to work on updating the looks, without re-implementing the actual CMS.

I had the idea of using Wordpress as a CMS, and by help from the RESTful API plugin I could serve the SPA with content. I also looked at Kirby, which is more simple than Wordpress along with the respective plugin for API-like handling of the data. I had used Kirby before, and felt I was able to have something working much quicker than when using Wordpress.

Along came Contentful, I heard it for the first time from a colleague who was using it. I read more about it and understood it was the perfect match for me.

A developer-friendly, API-first CMS

Contentful is focused on delivering a service where content-focused people can work with the content, and the developers with serving the content to their chosen application. It works with many languages and has a lot of examples for each language to dig into, to get you started.

With read and write SDKs you can set up Contentful exactly how you want it. I use their own web interface to fill out data, and then fetching the data by using their JavaScript client, everything from text to media assets. Via their global CDN (content delivery network) Contentful makes sure that the content is served with minimal latency.

Pricing

I use their free plan which is enough for me. Included is 50k API calls, 3 users and 10k of records. A record is either a media asset, or a content entry (for example a page). If you need to scale up you can find their pricing model here.

APIs

Contentful has four APIs as the core:

  • Content Delivery API (retrieving content)
  • Content Management API (creating, updating and deleting content)
  • Preview API (draft content)
  • Images API (image service, such as fetching media in a specific size/format)

Space

When logging in to Contentful and the editor for the first time you are asked to create a space. A space is where everything related to a project is, you can see it as a repository but of content. You can of course have multiple spaces under one account in Contentful.

Content Type

When done with creating a space for your project, you will be asked to create your first content type. A content type is your set structure for a content, which defines what kind of data values you can insert. For example my case content type consists of, among others, a short text for title, a longer text for body and assets for the screenshots.

All of your content types are grouped under what is called the content model. A content type is having a limit of 50 fields.

Content type

Content

Links

Linking content is key for my application since I have subpages of content. With a query parameter, I can get all related content to an entry, which is great when splitting up content in Contentful.

For example the items in my project list have their own content type, instead of inserting all items in the entry of project list in Contentful.

For example the items in my project list have their own content type, instead of inserting all items in the entry of project list in Contentful.

Every response in Contentful will have the sys property and it contains system metadata. The following is just a simple example in how a response can look and will be different depending on how you set up the content types, and which endpoints you will call upon in Contentful.

// Request Method: GET
// Request URL: //cdn.contentful.com/spaces/SPACE_ID/entries?content_type=project_list

{
  "sys": {
    "type": "Array",
    "id": "project_list"
  },
  "items": {
    0: {
      "projects": [
        {
          "sys": {
            "type": "Link"
          },
          "fields": {
            "title": "Blocket internal",
            "slug": "blocket-internal"
          }
        }
        ...
      ]
    }
  },
  "total": 10
}

Contentful + Redux

When you are done with your content types and have added some actual content it's time to serve it. Since my portfolio was built upon React & Redux I installed their JavaScript client as a dependency and started to move out my static data file piece by piece.

With Redux, I store the data from Contentful in the application state, so that every component knows when to update if new data is processed. For example the following action is fetching data by content type of project.

When the data is mapped to the props of my components, they can easily show the user the content that is published in Contentful.

Service

// services/contentful.js

import { createClient } from 'contentful';

const client = createClient({
  space: 'portfolio',
  accessToken: 'ACCESS_TOKEN',
  host: 'cdn.contentful.com'
});

export default client;

Action

// actions/contentful.js

import client from 'services/contentful';

const prefix = 'Contentful: ';

const REQUEST_ALL_PROJECTS = `${prefix} Request all projects.`,
      RECEIVE_ALL_PROJECTS = `${prefix} Receive all projects.`;

function requestAllProjects() {
  return {
    type: REQUEST_ALL_PROJECTS
  };
}

function receiveAllProjects(payload) {
  return {
    type: RECEIVE_ALL_PROJECTS,
    payload
  };
}

export function fetchAllProjects() {
  return (dispatch) => {
    // Handle request of data
    dispatch(requestAllProjects());

    // Get all entries by content type of `project` from CMS and sort by year
    client().getEntries({ content_type: 'project', order: '-fields.year' })
      .then(response => dispatch(receiveAllProjects(response)))
      .catch(error => dispatch(receiveAllProjects(error)));
  };
}

export {
  REQUEST_ALL_PROJECTS,
  RECEIVE_ALL_PROJECTS
};

Reducer

// reducers/contentful.js

import {
  REQUEST_ALL_PROJECTS,
  RECEIVE_ALL_PROJECTS,
} from 'actions/contentful';

const initialState = {
  projects_loaded: false,
  projects: [],
  error: null
};

const reducer = (state = initialState, { action, payload, error }) => {
  switch(action) {
    case REQUEST_ALL_PROJECTS:
      return {
        ...state,
        projects_loaded: false,
        error: false,
      }
    case RECEIVE_ALL_PROJECTS:
      if(error) {
        return {
          ...state,
          error: true
        }
      }

      return {
        ...state,
        projects_loaded: true,
        projects: payload.items,
        error: false
      }
  }
};

Component (dumb)

// components/project_list.js

const ProjectList = ({ items }) => (
  <ul>
    {items.map((item, index) => (
      <li key={index}>
        {item.title}
      </li>
    ))}
  </ul>
);

export default ProjectList;

There is more

I only touched a small part of Contentful but there is much more you can do with their service such as working with versioning and drafts, offline support with Sync API, localizing content, build your own editor with the Content Management API etc.

Get started

Contentful has a great developer center with substantial examples of what you can do with their service along with great documentation. Here are some links which I found useful when setting up Contentful for the first time: