
Rendering Contentful Rich Code Snippets in Gatsby

Gatsby and Contentful make for an incredibly powerful combination: all the benefits of an extremely fast static site, built on a modern framework, with a CMS which provides a very generous free tier (although you could argue that the prices from thereon up are very steep), and an interface which clients quickly adapt to. There is even an official source plugin (gatsby‑source‑contentful) to make connecting to, and reading from, Contentful all the more straightforward.
It is the same Gatsby and Contentful pattern I used on IMG Licensing, Wreel Agency, Red Central, and ToyBoxX, where a static front end and a headless CMS gave clients editorial control without sacrificing performance.
However, for tech‑type bloggers and writers, there is a problem with the way that Contentful's rich text outputs code blocks, and it has gone unresolved since 2019. Namely that it is not straightforward to render blocks or snippets of code from within Contentful rich text.
At a high level: rich content comes through as an object. Each node within the object corresponds to a block of copy (a paragraph, a title, an image, etc). When implementing a component to render these, you use @contentful/rich‑text‑types to define how each node type is displayed, and then pass that into @contentful/rich‑text‑react‑renderer to render the result into React.
For more a detailed overview of how these two technologies come together, you can read Contentful's own excellent article here. This is exactly how the content here on my personal website and blog works.
Inline Code
rich‑text‑types breaks down the content into:
BLOCKS: essentially block‑level elements: paragraphs, headings, lists, embedded assets (images), etc;INLINES: primarily these are just hyperlinks although you can also have inline embedded assets;MARKS: these are inline text formats: bold, italic, underlined, and ‑ of most interest to us in this article ‑ code.
To wrap these different content types, we simply use renderMark to determine how each is handled, for example:
renderMark: { [MARKS.BOLD]: text => <b>{text}</b>, [MARKS.ITALIC]: text => <i>{text}</i>, [MARKS.UNDERLINE]: text => <u>{text}</u>, [MARKS.CODE]: text => <code>{text}</code>,}I like to add a check here to make sure that there is actually content within each before rendering so that we don't end up with empty elements on the page:
[MARKS.CODE]: text => { if (text.length > 0) { return <code>{text}</code>; }}This handles inline code snippets: those which appear within the flow of a paragraph. Just like this.
Actual Blocks of Code
What this does not do is provision for larger, stand‑alone blocks of code, as you will see featured frequently within my blog. As I mentioned previously Contentful breaks the rich text object into nodes that roughly correlate to block‑level elements, but they do not provide a 'code' BLOCK type.
What this means is that when you create a code block in Contentful, what you are actually doing is creating paragraphs, and then embedding a single 'code' mark within it. In our renderer it is relatively straightforward to detect these:
Paragraph nodes of this type will only have a single content node (conversely: normal paragraphs of text can have several if they have inline marks within them), and the marks field for the first content element with be type: 'code'. With this in mind, it is possible to output code blocks inside <pre> (and <code> if you like), whilst also outputting normal paragraphs wrapped in <p>:
[BLOCKS.PARAGRAPH]: (node, children) => { if ( node.content.length === 1 && find(node.content[0].marks, { type: 'code' }) ) { return <pre><code>{node.content[0].value}</code></pre>; } return <p>{children}</p>;}Adjacent Blocks of Code
This will then get you almost the entire way there. The only issue is that because Contentful splits code blocks up essentially by 'paragraph', if you have a large code block in your content with line breaks, what you will end up with is multiple, adjacent <pre> tags in the output ‑ even though from within Contentful these appear as part of the same single code block.
This may not be an issue for you at all depending on how you choose to lay out your page, but for me it left gaps in between each, breaking up the code block into sections. For example, a screenshot of a single code block from a previous article with lime outlining around each <pre> tag to show the separation:

Fortunately, you can work around this with a little CSS and the adjacent sibling combinator to remove the top padding and pull the adjacent blocks upward to fill the empty gap:
.rich-code { $margin--small: 3rem; margin: (1.5 * $margin--small) (-1 * $margin--small); padding: $margin--small; & + & { margin-top: (-2 * $margin--small); padding-top: 0; }}I am by no means a fan of negative margins, but in this instance, it does the trick just fine:

At this point, you may already have the solution you need. For me, this was a compromise that worked: it visually looked ok, but it resulted in non‑semantic markup, and individual sections within the code block would wrap at different points on narrower screens. I had to use white‑space: pre‑wrap to combat this, but could not do anything more interesting with my code, such as syntax‑highlight it or allowing horizontal scrolling on small screens.
Combining Adjacent Snippets of Code
The final piece of this puzzle is relatively straightforward, but eventually needed the data‑transformation wizardry of my good (and very talented) friend Ben Stokoe to implement.
Essentially, the object that Contentful provides needs to be transformed so that adjacent, code‑only, paragraph nodes are combined together into a single node, with each separated by /n/n.
The final transformation code does exactly that, and looks like this:
const mergeCodeBlocks = data => { const mergedData = []; const checkForType = arrayToCheck => arrayToCheck?.content?.length === 1 && arrayToCheck?.content[0]?.marks?.length && arrayToCheck?.content[0]?.marks[0]?.type === 'code'; for (let i = 0; i < data.length; i++) { const current = data[i]; if (checkForType(current)) { const codeBlock = current; let j = i; while (data[j + 1] && checkForType(data[j + 1])) { codeBlock.content[0].value = `${codeBlock.content[0].value}\n\n${data[j + 1].content[0].value}`; j++; } mergedData.push(codeBlock); i = j; } else { mergedData.push(current); } } return mergedData;};If you pass the data through that before passing it on to documentToReactComponents() ‑ and using the renderMark configuration we talked about earlier ‑ then your block‑level code snippets will come through as single items rather than split out over multiple 'paragraph' fragments.
One thing to bear in mind: this is relatively intense data manipulation. You should make sure that your React component does not re‑manipulate the same data again every time it updates, especially as this could in theory result in your content becoming altogether malformed. There are any number of different ways of achieving this. Personally, I use an Effect Hook to manipulate the data once, and then store that in state for future updates.
Related Articles

What are Array‑Like Objects in JavaScript? 
Removing p Tags from Contentful List Items. Removing
pTags from Contentful List Items
Angular Change Detection: How It Works and How to Optimise It. Angular Change Detection: How It Works and How to Optimise It

If Not Internet Explorer Conditional HTML. If Not Internet Explorer Conditional HTML

Converting Between Camel, Snake, and Kebab Case in JavaScript. Converting Between Camel, Snake, and Kebab Case in JavaScript

Solving the LeetCode 'Binary Tree Zigzag Level Order Traversal' Problem. Solving the LeetCode 'Binary Tree Zigzag Level Order Traversal' Problem

Implementing Server‑Side Rendering (SSR) in Vue. Implementing Server‑Side Rendering (SSR) in Vue

Single Number in TypeScript with Bit Manipulation. Single Number in TypeScript with Bit Manipulation

Using Regex to Replace Numbers in a String. Using Regex to Replace Numbers in a String

Using Container Queries in CSS. Using Container Queries in CSS

Caching Strategies in React. Caching Strategies in React

Optional Chaining in JavaScript (?.). Optional Chaining in JavaScript (
?.)