You're building a blog post page in Next.js. Your CMS returns body content as an HTML string: <p>Here is some text</p><h2>A heading</h2>. You inject it with dangerouslySetInnerHTML. It works, but now your heading style is controlled by a global CSS class, not your component system. You can't add a custom anchor link to each heading. You can't intercept internal links to use Next.js Link. The HTML string is a black box.
Why Structured JSON Is Better
ContentGrid stores rich text as a document tree — a JSON object with nodes, each with a type and optional marks. A heading looks like {"type": "heading", "level": 2, "content": [{"type": "text", "value": "A heading"}]}. Your frontend receives the structure, not the rendered output. You decide how each node type maps to a React component.
This matters because your frontend's design system owns the rendering. An h2 in your blog post should use your Heading component with the right size, font weight, and anchor ID. An internal link should use Next.js Link for client-side navigation. A code block should use your syntax highlighter. None of that is possible when the CMS returns pre-rendered HTML.
Building a Rich Text Renderer
- Create a top-level
RichTextcomponent that accepts the document JSON and maps each node type to a component. - Handle text marks (bold, italic, code) as wrapper elements inside text nodes.
- Intercept link nodes to check if the URL is internal — if so, use Next.js
Link; if external, render a standard anchor withtarget='_blank'. - Render embedded entries (images, callout blocks, embedded content types) as full React components, not HTML strings.
ContentGrid's TypeScript SDK exports the document node types so your renderer can be fully typed. Every branch of your switch statement has a TypeScript type to match.
Embedded Entries as Rich Text Nodes
One of the most useful features of structured rich text is embedded entry support. You can embed a CodeExample entry, a ProductCallout entry, or a VideoEmbed entry inline within body content. ContentGrid includes those as nodes in the JSON document, resolved with the entry's fields. Your renderer maps the embedded entry type to the appropriate React component, keeping complex interactive elements out of the editor while still letting content creators place them contextually.
The result is rich text that's as flexible as your component library allows, without a single dangerouslySetInnerHTML in sight.
Ready to start tracking your competitors?
ContentGrid automatically monitors competitor websites, emails, and social media — and delivers structured intelligence straight to your inbox.