Rendering CMS Rich Text Safely in Gatsby and React

Hero image for Rendering CMS Rich Text Safely in Gatsby and React. Image by Nick van den Berg.
Hero image for 'Rendering CMS Rich Text Safely in Gatsby and React.' Image by Nick van den Berg.

CMS rich text is not the same thing as a safe blob of HTML.

In Contentful and similar CMSes, rich text is structured data. It represents paragraphs, headings, lists, links, embedded entries, assets, and marks. Gatsby and React then need to decide how those nodes become components.

That decision is where a lot of frontend quality lives.


Map Nodes Deliberately

Do not treat rich text rendering as a oneline utility that gets forgotten.

Decide how to render:

  • paragraphs
  • headings
  • unordered lists
  • ordered lists
  • links
  • embedded assets
  • embedded entries
  • code blocks
  • quotes

The Contentful rich text documentation explains the structure. The frontend job is to map that structure to the project's HTML and React components.


Keep Heading Levels Under Control

Rich text fields can create heading problems if editors can choose any level anywhere.

An article title may already be the page h1. Rich text headings should then usually start below that. If an editor inserts another h1 inside the body, the document structure becomes less clear.

The renderer can enforce or normalise heading rules, but the CMS model and editorial guidance should help too. Do not make the renderer responsible for silently fixing every content problem.


Links in rich text need different treatment depending on their type:

  • external URL
  • internal entry link
  • asset link
  • email link
  • anchor link

For internal entries, prefer resolving the entry to a real site URL rather than leaving CMS IDs in frontend code. For external links, decide how to handle target, rel, and visible link text.

Broken references should fail clearly during build or render a safe fallback. A missing linked entry should not produce an empty anchor.


Treat Embedded Entries as Components

Embedded entries are where rich text becomes more than formatted copy.

They might represent:

  • code snippets
  • callouts
  • image blocks
  • video embeds
  • related articles
  • product cards

The article on rendering Contentful Rich Code snippets in Gatsby is one example. A code snippet entry needs syntax, language, caption, and safe markup. It should not be treated like an ordinary paragraph.


Avoid Unsafe HTML Shortcuts

React makes it possible to inject HTML directly, but rich text usually gives you a safer option: render known nodes to known components.

Avoid bypassing that structure unless there is a strong reason. Direct HTML injection raises questions about sanitisation, allowed tags, and editor permissions.

Structured rich text is useful precisely because the frontend renderer can control what each node type becomes.


Test Awkward Content

Rich text renderers need real test cases:

  • nested lists
  • empty paragraphs
  • long links
  • embedded entries in lists
  • missing embedded assets
  • code blocks
  • bold and italic marks
  • internal entry links
  • unsupported node types

These are the kinds of small rendering issues that only appear when real rich text is involved. A renderer that works for three neat paragraphs has not really been tested yet.

Rich text rules also affect preview and migration work. Planning content models for a headless CMS covers the modelling side, and fixing Contentful preview in Next.js Draft Mode covers the later preview problem in a Next.js workflow.


Wrapping Up

Rendering CMS rich text safely is about keeping control of the translation from content model to frontend output.

Map nodes explicitly, handle links and embeds carefully, avoid unsafe shortcuts, and test the messy cases. Rich text is powerful when it gives editors flexibility without making the rendered page unpredictable.

Key Takeaways

  • CMS rich text should be rendered through explicit node mappings.
  • Heading levels need rules that fit the surrounding page template.
  • Internal and external links need different rendering decisions.
  • Embedded entries should become deliberate React components.
  • Test nested, missing, unsupported, and awkward rich text cases.

Planning a platform change?

I help teams make difficult platform work clearer, from architecture decisions and migrations to launch recovery, performance, and search visibility.