Rich Text HTML Renderer

Converts Contentful Rich Text documents into clean, customizable HTML strings.

Library
npm
v17.2.3
583stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
92/100Excellent
Development Activity100
Maintenance100
Community80
Maturity60
Momentum28

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
71/100Good
Architecture82
Code Quality88
Innovation55
Learning Curve60

@contentful/rich-text-html-renderer turns the JSON-based Rich Text field type from Contentful’s CMS into an HTML string, handling every standard node type — paragraphs, headings, lists, tables, quotes, embedded entries and assets, and inline hyperlinks — out of the box. It ships as part of the official contentful/rich-text monorepo alongside the type definitions, plain-text renderer, and React renderer, so it shares the same Document/Block/Inline types and BLOCKS/INLINES/MARKS constants used across the Contentful ecosystem.

Rather than hard-coding markup, the library exposes renderNode and renderMark override maps that merge with sensible defaults, letting consumers replace how any single node or text mark is rendered without reimplementing the whole traversal. Options like preserveWhitespace and stripEmptyTrailingParagraph handle the practical edge cases teams hit when moving Rich Text content into static HTML, emails, or server-rendered pages, and every text value is HTML-escaped by default to guard against injection from user-authored content.

What You Get

  • Default HTML renderers for every standard Rich Text block and inline node (paragraphs, headings 1-6, lists, tables, blockquotes, embedded entries/assets, hyperlinks)
  • Overridable renderNode and renderMark maps that merge with the defaults so you only replace what you need
  • Built-in HTML escaping (escapeHtml) for text values, attribute values, and asset URLs/descriptions
  • preserveWhitespace option to convert line breaks and repeated spaces into <br/>/  sequences
  • stripEmptyTrailingParagraph option to drop the empty trailing paragraph Contentful’s editor often appends

Common Use Cases

  • Rendering a Contentful Rich Text field directly to HTML for a server-rendered page or static site
  • Generating HTML email bodies from Rich Text content authored in Contentful
  • Customizing how embedded entries/assets render (e.g. swapping the default <div>/<img> output for themed component markup)
  • Enforcing consistent HTML escaping when Rich Text content includes user-generated or untrusted text

Under The Hood

Architecture The package is a single-file recursive tree-walker: documentToHtmlString (src/index.ts) merges the caller’s optional renderNode/renderMark maps over defaultNodeRenderers/defaultMarkRenderers, then delegates to nodeListToHtmlString and nodeToHtmlString, which recursively distinguish text nodes from block/inline nodes and thread the merged renderer maps through closures rather than a class or DI container. There is no separate rendering pipeline stage — the recursion itself is the pipeline — so changing the core traversal in nodeToHtmlString would ripple into every consumer’s custom NodeRenderer/RenderMark signatures, since those types are exported and threaded through the whole call chain.

Tech Stack Written in TypeScript, compiled with tsc and bundled by Rollup (rollup.config.js) into ES5/ESM builds plus a types bundle, managed as an Nx-orchestrated pnpm workspace (nx.json, pnpm-workspace.yaml) alongside sibling packages in the same monorepo. Its only runtime dependency is the sibling @contentful/rich-text-types package for shared Document/Block/Mark types and BLOCKS/INLINES/MARKS constants. CI runs through GitHub Actions (ci.yml, check.yaml, build.yaml, release.yaml) with commitlint and husky enforcing conventional commits for release automation.

Code Quality A single, extensive test file (src/test/index.test.ts) exercises every default node and mark renderer, override behavior, escaping, preserveWhitespace, and stripEmptyTrailingParagraph, with fixture documents factored into a separate documents/ directory rather than inlined. Tests run under @swc/jest with jest-junit reporting for CI consumption. TypeScript interfaces (RenderNode, RenderMark, Options, Next) give consumers compile-time safety on custom renderers, and the monorepo enforces ESLint plus Prettier via lint-staged/husky pre-commit hooks.

What Makes It Unique The library isn’t algorithmically novel — it’s a straightforward tree-walk-to-string serializer — but its renderer-override design (a shallow merge of caller overrides over ~20 defaults, for both nodes and marks) lets a consumer replace exactly one rendering rule without redefining the rest, and it bakes in Contentful-specific edge cases — trailing empty paragraph stripping, escaped asset URLs/descriptions, default markup for embedded entries/assets/resources — that generic Markdown-to-HTML renderers don’t address.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search