Render content
Turn a published block tree into React UI with a typed block map.
This guide renders a published block tree in React. It assumes you can fetch content with getPublishedContent (see Use with Next.js).
Map every block type to a component
createBlocksMap takes a collection definition and a component for every block type in it. Each component's properties are typed from that block's definition:
import { createBlocksMap } from '@createcms/core/react/blocks';
import { collections } from '@/lib/collections';
export const pageBlocks = createBlocksMap(collections.pages, {
hero: ({ properties }) => <h1>{properties.headline}</h1>,
richText: ({ properties }) => (
// `richText` stores markup; render it as HTML (sanitize if untrusted).
<div dangerouslySetInnerHTML={{ __html: properties.content }} />
),
});richText is a raw, unsanitized HTML string. createcms stores it verbatim (it is typed as string) and ships no sanitizer, so dangerouslySetInnerHTML renders whatever markup the value holds. When that HTML comes from a source you do not fully control (an author, a content import, a third-party API), a payload like <img src=x onerror="…"> runs script in your users' browsers: a stored-XSS hole. Sanitize richText before rendering it, as shown in Sanitize untrusted HTML before rendering.
Sanitize untrusted HTML before rendering
createcms is headless: it stores richText as an opaque HTML string and never sanitizes it, so cleaning that markup before it reaches the DOM is your responsibility (see Security). Run every richText value, and any other untrusted HTML, through a sanitizer such as DOMPurify or sanitize-html inside the block component:
import DOMPurify from 'isomorphic-dompurify';
import { createBlocksMap } from '@createcms/core/react/blocks';
import { collections } from '@/lib/collections';
export const pageBlocks = createBlocksMap(collections.pages, {
hero: ({ properties }) => <h1>{properties.headline}</h1>,
richText: ({ properties }) => (
<div
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(properties.content) }}
/>
),
});isomorphic-dompurify runs the same sanitizer in the browser and in Server Components, so it is safe in the server-rendered Page below. On a Node-only path sanitize-html works too; plain dompurify needs a browser DOM, so use it only from 'use client' components.
Render the tree
Pass the map and a tree to BlocksRenderer:
import { BlocksRenderer } from '@createcms/core/react/blocks';
import { pageBlocks } from '@/lib/blocks';
import { cms } from '@/lib/cms';
export default async function Page() {
const { variants } = await cms.api.pages.getPublishedContent({
query: { path: '/welcome' },
});
return <BlocksRenderer blocks={pageBlocks} tree={variants[0].tree} />;
}Render only some block types
createBlocksMap and BlocksRenderer need a component for every block type. To render only a subset (or to embed references with their own components), use createContentRenderer, which accepts a partial map and returns a ready-made component:
import { createContentRenderer } from '@createcms/core/react/blocks';
import { collections } from '@/lib/collections';
const Content = createContentRenderer(collections.pages, {
hero: ({ properties }) => <h1>{properties.headline}</h1>,
});
// <Content tree={variants[0].tree} />Both renderers resolve embedded references the same way; createContentRenderer differs only by accepting a partial map.
Render a review diff
The same map also renders a merge-request diff: getDiff returns an annotated copy of the draft tree, and both renderers take an opt-in diff prop that wraps changed blocks in styleable data-diff markers. See Review changes visually.
Choosing a variant
getPublishedContent returns one entry per published branch in variants (see Publishing). With no A/B test there is exactly one, so variants[0] is the page. With the A/B testing plugin, resolve the visitor's branch and select it with pickVariant — see A/B testing → Usage.
For every React export and its props, see the React reference.