Marking up the Contentful contents

Posted last year
Return to overview

If you open the homepage, you're looking at a markup that looks a bit like this:

{ "nodeType": "document", "data": {}, "content": [ { "nodeType": "paragraph", "data": {}, "content": [ { "nodeType": "text", "value": "This is some text coming from the CMS", "marks": [], "data": {} } ] } ] }

This is a contract that Contentful provides to provide a structured response based on the content and not necessarily on the recipient. Luckily we have tools that can translate this into meaningful markup. Bear in mind also, that this content is only related to Rich Text Editor (RTE) fields and referral entries (in our case Tags on an Article).

Let's get to work!

In this step we already added some dependancies to the project, remember? Specifically these two:

@contentful/rich-text-html-renderer @contentful/rich-text-types

These are helper to render the RTE output to HTML markup. Since we're building a function for reuse, we're adding another composable!

Add these lines to the `composables/contentful.ts` (imports go at the top, function itself you can place where you like):

import { BLOCKS } from '@contentful/rich-text-types'; import { documentToHtmlString } from '@contentful/rich-text-html-renderer';

And the composable function the replace the document to HTML:

export const useDocumentToHtmlString = (document: any): string => { const options = { renderNode: { [BLOCKS.EMBEDDED_ENTRY]: (node: any) => `<div>EMBEDDED_ENTRY_NOT_CONFIGURED: ${(JSON.stringify(node))}</div>`, [BLOCKS.EMBEDDED_ASSET]: (node: any) => { const { title, file: { url }, } = node?.data?.target?.fields; return `<img src="https:${url}" alt="${title}" role="presentation" />` } } } return documentToHtmlString({ ...document }, options); }

This function uses basically the documentToHtmlString from Contentful, but it also adds some replacement (configured as the `options`). The options takes in the raw JSON blocks coming from the document and assesses whether it can match it to a typed key. What I've added here it a replacer for embedded images to be rendered as an `<img />` tag automatically. (The custom render behaviour also also documented here.)

Now at least we have a tool to convert the JSON to HTML. Now we're going to use it. For the sake of reusability, let's create a component that: takes in a raw JSON document and renders it as HTML so we can use it anywhere we like.

In the `components` folder, create a `DocumentToHtmlString.vue` component with the following contents:

<script lang="ts" setup> interface Props { document: object } const props = defineProps<Props>() const htmlString = useDocumentToHtmlString(props.document); </script> <template> <div v-html="htmlString"></div> </template>

As you see, it's not complicated. Let's apply the component in our existing pages. On the `ArticleBody.vue` and `PageBody.vue` components, replace the line with the `{{ body }}` expresion to use the new component and provide the `body` as the `document` property:

From:

{{ body }}

To:

<DocumentToHtmlString :document="body" />

You should see that the JSON has been replace with formatted HTML for all URLs (check out this step of the tutorial code when in doubt). You should even be able to show an embedded image now, due to the custom renderer we've added.

Now that we can actually read the contents of a page, we're going to add more internal linking (with Article Tags) in the next step.

Return to overview