Rob West

How To Render Rich Text Content From Kentico Kontent In Gatsby

Published 17 Jun 2020

This is a quick start guide explaining how you render rich text content from Kentico Kontent in Gatsby. All of the information on how to do this is available via the Gatsby Kontent Components GitHub page, but you have to dive into the linked article by Richard Shackleton and piece everything together, so I thought it would be worthwhile to set out a complete working example.

In a bit of a meta-fest, the code examples here are from this website which lives in a public repo on GitHub so you can review the complete working code.

Kontent's Rich Text Data Structure

Before we dive into the code, I'll just set out the background on Kentico Kontent. If you are familiar with the concepts you can skip this section.

A Kontent rich text field, called "body" in this example, has the following overall structure in the returned data:

"body": {
  "value": "",
  "images": [],
  "links": [],
  "modular_content": []
}

The value contains the raw HTML with unprocessed links, images and content items. Images are straightforward, and will actually display without any further work. They look like this:

<figure
  data-asset-id=\"ea7e7902-687b-4800-bc98-433ca0c2f124\"
  data-image-id=\"ea7e7902-687b-4800-bc98-433ca0c2f124\"
>
  <img
    src=\"https://assets-us-01.kc-usercontent.com:443/0012f3d4-cb4e-0087-cb65-5a2a05d484fc/d51c1aa2-56d2-4c78-9e5f-0528a738a0ba/5370_20190601_112442_159677033_original.JPG\"
    data-asset-id=\"ea7e7902-687b-4800-bc98-433ca0c2f124\
    data-image-id=\"ea7e7902-687b-4800-bc98-433ca0c2f124\"
    alt=\"Alt text\"
  >
</figure>

We can do better than this, but if you don't care about gatsby-image goodness and responsive images then you can crack on.  A link to another content item, on the other hand, will not work straight off the bat because the href is "\":

<a
  data-item-id=\"b85f4384-02e8-491e-992b-64a232c3e659\"
  href=\"\"
>
  Link Text
</a>

The details of the item are held in the links array, and we need to hydrate this link with the data from that array.

Modular content holds the components that you have placed in the rich text field. You can find more information about them in the Kontent docs article on structuring content with components. Such an embedded object will be represented like this in the raw output:

<object
  type=\"application/kenticocloud\"
  data-type=\"item\"
  data-rel=\"component\"
  data-codename=\"n74b56e82_ce32_0111_359b_92fb6593c025\"
>
</object>

As an aside, I bet the Kentico guys hate that they still have this reference to the old name for the product baked into the system 😱.

So again, that's not going to render without some additional work to hydrate the object. So how do you go turning this raw data into something useful? I'm going to start from the point of a working Gatsby site hooked up to the Gatsby source Kontent plugin. There are plenty of good articles on how to do this so I am not going to cover it here.

Step 1: Install The Kontent Components Package

First up we need the components package:

npm install @kentico/gatsby-kontent-components
yarn add @kentico/gatsby-kontent-components

Step 2: Setup your GraphQL Query

In order to work with the rich text component we need some mandatory fields in the GraphQL. A simple example retrieving data for all types of rich content might look like this:

query MyQuery {
  allKontentItemArticle(sort: {order: DESC, fields: elements___publish_date___value}) {
    nodes {
      elements {
        body {
          value
          modular_content {
            ... on kontent_item_blockquote {
              id
              elements {
                text {
                  value
                }
              }
              system {
                codename
                type
              }
            }
            ... on kontent_item_code_block {
              id
              elements {
                language {
                  value
                }
                code {
                  value
                }
              }
              system {
                codename
                type
              }
            }
          }
          links {
            link_id
            url_slug
          }
          images {
            image_id
            fluid(maxWidth: 1000) {
              ...KontentAssetFluid
            }
            description
          }
        }
      }
    }
  }
}

I have two components that I use in my articles, a simple blockquote and a code display component that uses highlight.js for syntax highlighting.

Step 3: Use the RichTextElement Component

In the absolute simplest case, you can just pass the value field to the component like this:

import { RichTextElement } from '@kentico/gatsby-kontent-components';
...
<RichTextElement value={richTextElement.value} />

Which is what I'm doing on my philosophy page at the moment because it does not use any rich text components. If that was all it did you'd rightly wonder what the point of it was, you could just render the raw HTML via dangerouslySetInnerHTML. The real value comes for the other types of rich content. With just HTML we cannot use React components to render our items, which is the whole point of using a React-based platform like Gatsby. The approach taken is fully described in Richard Shackleton's article, but in summary it uses html-react-parser to turn HTML into React components. This parser has a replace callback which allows you to swap an HTML element with another React element, and RichTextElement acts as a wrapper around this to do all the heavy lifting for you.

For each of the rich text types RichTextElement provides props for the value and a resolver function which is passed into the replace callback to return a full fat React component. The component uses the additional meta data such as data-item-id in the raw HTML to link up the information with the additional data provided in the separate fields for images, links and modular content.

Images

If you want to modify images then you need to have returned the image_id as a minimum in your GraphQL query as indicated above. The example implementation on the Kontent repo just returns a native HTML img tag, which misses the point of the component. The power of Gatsby is its use of React, and one of its really nice features is gatsby-image which will render lazy loaded responsive images for you. Normally gatsby-image needs the image files to be on the file system, but Richard Shackleton has also written a plugin that allows you to use it for Kontent called gatsby-transformer-kontent-image. In my view this should be a core feature in the Kontent components repo, but for now I will walk you through using this component. First up, we need to install the packages:

npm install --save gatsby-image
npm install --save @rshackleton/gatsby-transformer-kontent-image
yarn add gatsby-image
yarn add @rshackleton/gatsby-transformer-kontent-image

Add the plugin to your gatsby-config.js:

module.exports = {
  plugins: [
    {
      resolve: `@rshackleton/gatsby-transformer-kontent-image`,
      options: {
        remote: true,
      },
    },
  ],
};

In the example GraphQL query above you will see that I already referenced gatsby-image via the fluid keyword. This uses a GraphQL query fragment to configure the required data. Don't forget that we also need image_id field as this is used to link the raw value to the Kontent image. We are now in a position to create our gatsby-img React component:

import Image from 'gatsby-image';
...
const richTextElement = data.kontentItemArticle.elements.body;
...
<RichTextElement
  value={richTextElement.value}
  images={richTextElement.images}
  resolveImage={image => (
      <Image
        fluid={image.fluid}
        alt={image.description}
      />
  )}
/>

and you now have rich text content with beautiful responsive and lazy loaded images.

Links

Compared to images, links are pretty simple to deal with. You need the link_id and url_slug fields in the GraphQL query, and if you are not doing anything particularly complicated then your code can look like this:

import { graphql, Link } from 'gatsby';
...
<RichTextElement
  value={richTextElement.value}
  links={richTextElement.links}
  resolveLink={(link, domNode) => (
    <Link to={`/articles/${link.url_slug}`}>
      {domNode.children[0].data}
    </Link>
  )}
/>

Here we are passing a resolveLink function which will create a Gatsby link to take advantage of the React ecosystem to avoid full page reloads for internal links. You can do more complex link URL logic, but for my blog any linked item is an article so I am safe with this approach. You get the idea.

LinkedItems

The way you need to deal with components is probably the area where the example from Kentico is most vague. This array can contain any of the content item types in your project. So your code needs to handle the mapping. I've followed Richard's approach and delegated this to a LinkedItem component, so my RichTextElement looks like this:

import LinkedItem from '../components/LinkedItem';
...
<RichTextElement
  value={richTextElement.value}
  linkedItems={richTextElement.modular_content}
  resolveLinkedItem={linkedItem => (
    <LinkedItem linkedItem={linkedItem} />
  )}
/>

and the LinkedItem component then contains a switch statement for each type of content item that I want to use. We pulled this type information in our GraphQL query above. The component then transforms the linked item data into the appropriate props for the type. I'm in the process of moving to TypeScript and will introduce more validation at that point, but you get the idea:

import React from 'react';
import Blockquote from '../Blockquote';
import CodeBlock from '../CodeBlock';

const LinkedItem = ({ linkedItem }) => {
  const type = linkedItem.system.type;

  switch (type) {
    case 'blockquote': {
      return <Blockquote quote={linkedItem.elements.text.value} />;
    }

    case 'code_block': {
      const {
        elements: {
          language: { value: language },
          code: { value: code },
        },
      } = linkedItem;
      return <CodeBlock language={language} code={code} />;
    }

    default:
      return null;
  }
};

export default LinkedItem;

You can find the full working example on my article template

Wrap Up

You now have everything you need in place to be able to render your own components and rich text. This approach allows you to make your components first class React citizens with nothing special related to Kontent in them. Any wiring up is handled in either the resolver functions on the linked items component. Happy coding!

© 2024 Rob West. All Rights Reserved. Built using Kontent and Gatsby.