James Mugliston

James Mugliston

Headless CMS Integration

Recently I've been looking at the integration of headless content management systems, and how they can be used to create and manage content across websites, apps or other digital platforms.

What is a Headless CMS?

If you're used to monolithic CMS systems like WordPress, the content storage backend is tightly coupled with the presentation layer e.g. WYSIWYG editor and theme plugins. I've used WordPress for several websites, and love how simple it is for developers and editors to get started with useful features like editing and live preview, however, this comes at the cost of flexibility. A headless CMS is different because the presentation layer (head) is separate from the backend content repository (body). Additionally, it provides greater control over how content is served, which makes it more flexible for frontend developers to present content across various digital channels (e.g. web, mobile etc.). Also, accessing content via an API is great for reusability, security and scalability when building digital platforms.

Choosing the Right CMS

With so many options available, finding a CMS that is suitable for what you need can be challenging. It's important to look at criteria such as usability, customisation options and pricing. I'm using Contentful to manage this blog because it provides a good experience for content editing, easy integration with modern application frameworks and a free tier for personal websites!

Using the Contentful API

There are multiple APIs that Contentful exposes for different purposes, but for the following examples, I am using the content delivery API (a read-only API for delivering JSON data).

There are a range of options you can use to fetch data from the delivery API.

Rest API

A standard REST endpoint is the easiest to query:

curl -X GET https://preview.contentful.com/spaces/{space_id}/environments/{environment_id}/entries?access_token={access_token}

GraphQL

GraphQL is great for fetching the specific fields you need from the content API. There is even a "playground" app that is great for testing queries.

An example GQL query:

curl -g \
-X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <PREVIEW-API-TOKEN>" \
-d '{"query": "query { blogPostCollection { items { title date text { json } } } }"}' \
https://graphql.contentful.com/content/v1/spaces/<SPACE ID>

Below is a preview of what the "GraphQL playground" app looks like on the Contentful UI. With the free tier, you are limited on query complexity so remember to set sensible limits on queries that could return a lot of items!

Example of what the GraphQL playground app looks like in Contentful.

Contentful Client

The Contentful client also provides an easy way to access content using a language native to your application. See below for an example of how to set up and fetch content in JavaScript.

import dotenv from "dotenv";
import contentful from "contentful";

dotenv.config();

const client = contentful.createClient({
  space: process.env.CONTENTFUL_SPACE_ID,
  accessToken: process.env.CONTENTFUL_ACCESS_TOKEN,
});

const entries = await client.getEntries({
  skip: 0,
  limit: 1,
  order: "sys.createdAt",
});

Integration with Next.js

Once you have the content you need from the API, it's simple to integrate this with a front-end framework like Next.js.

Below is an example component that will display content from a rich text field in Contentful.

import { documentToReactComponents } from "@contentful/rich-text-react-renderer";
import { BLOCKS, INLINES } from "@contentful/rich-text-types";
import Image from "next/image";

const customMarkdownOptions = (content) => ({
  renderNode: {
    [INLINES.HYPERLINK]: ({ data: { uri }, content }) => (
      <a className="link" rel="noreferrer" target="_blank" href={uri}>
        {content[0].value}
      </a>
    ),
    [BLOCKS.EMBEDDED_ASSET]: (node) => {
      const id = node.data.target.sys.id;
      const assets = content.links.assets.block;
      const asset = assets?.find((asset) => asset.sys.id === id);

      if (asset?.url) {
        return (
          <Image
            // Use a really basic image loader
            loader={({ src }) => src}
            src={asset.url}
            layout="fill"
            alt={asset.description}
            width={asset.width}
            height={asset.height}
          />
        );
      }

      return null;
    },
  },
});

const RichText = ({ content }) => {
  return (
    <div>
      {documentToReactComponents(content.json, customMarkdownOptions(content))}
    </div>
  );
};

export { RichText };

Contentful returns a list of rich text 'nodes' in the API response, which represent different parts of the content e.g. paragraphs, images and links. In the example, you can see how I added some custom rendering logic for hyperlinks and images in React.

Using this pattern you can easily fetch and display any content you need from the CMS into a frontend application.

If you want to see a more complete example of integrating Contentful with Next.js, there is a great example project available on GitHub here.

Summary

A headless CMS is a great choice if you want more flexibility over the presentation of content in your applications. As demonstrated, tools like Contentful with easy and simple API access, provide a lot of value when building modern, content-rich applications.