How to properly render Portable Text with toHTML in Sanity?
I'd be happy to walk you through setting up Portable Text! Looking at your code, I can see you're on the right track but there are a few issues to fix. Let me break this down step by step.
Your Schema is Perfect
Your schema is exactly right:
{
name: "text",
title: "Text",
type: 'array',
of: [ { type: 'block' } ]
}This creates a Portable Text field that stores rich text as structured JSON. Now let's fix the rendering part.
The Main Issues in Your Code
- You're passing
{ blocks: text }totoHTML, but the function expects the array directly as the first argument - You have a reference to
blocksvariable that doesn't exist (blocks.map(...)) - String manipulation with
.replace()is fragile - use thecomponentsoption instead
The Correct Way to Use @portabletext/to-html
The toHTML function takes two arguments:
- Your Portable Text array directly (not wrapped in an object)
- An options object where you can customize rendering with
components
Here's your corrected code:
import { toHTML } from '@portabletext/to-html';
const BodyText = ({ text, layoutSettings } = {}) => {
let className = 'body-text';
let ls = 'col-6';
if (layoutSettings?.sizing) {
ls = layoutSettings.sizing;
className = `${className} ${ls}`;
}
if (!text || text.length === 0) {
return `<p class="${className}"></p>`;
}
// Pass the array directly, then customize with components
const html = toHTML(text, {
components: {
block: {
// Customize how normal paragraphs render
normal: ({ children }) => `<p class="${className}">${children}</p>`,
// Add other block styles if you use them
h1: ({ children }) => `<h1 class="${className}">${children}</h1>`,
h2: ({ children }) => `<h2 class="${className}">${children}</h2>`,
},
},
});
return html;
};
export default BodyText;Understanding the Components Option
The components option is how you customize your HTML output. It has three main sections:
1. Block Components
For paragraph-level content with different styles:
components: {
block: {
normal: ({ children }) => `<p class="${className}">${children}</p>`,
h1: ({ children }) => `<h1>${children}</h1>`,
h2: ({ children }) => `<h2>${children}</h2>`,
blockquote: ({ children }) => `<blockquote>${children}</blockquote>`,
}
}2. Mark Components
For inline formatting like bold, italic, and links:
components: {
marks: {
strong: ({ children }) => `<strong>${children}</strong>`,
em: ({ children }) => `<em>${children}</em>`,
link: ({ children, value }) => `<a href="${value.href}">${children}</a>`,
}
}3. Type Components
For custom block types (like images) you might add later:
components: {
types: {
image: ({ value }) => {
return `<img src="${value.asset.url}" alt="${value.alt || ''}" />`;
},
}
}If You're Using React
If this is actually a React component (your code looks like it might be), I'd strongly recommend using @portabletext/react instead - it's much cleaner:
import { PortableText } from '@portabletext/react';
const BodyText = ({ text, layoutSettings }) => {
const className = layoutSettings?.sizing
? `body-text ${layoutSettings.sizing}`
: 'body-text col-6';
return (
<PortableText
value={text}
components={{
block: {
normal: ({ children }) => <p className={className}>{children}</p>,
},
}}
/>
);
};The React version gives you better performance and lets you use actual React components for custom blocks, which is much more maintainable than string manipulation.
Key Takeaway
The most important thing to remember: toHTML(portableTextArray, options) - pass your array directly as the first argument, then use the components option in the second argument to customize how each piece renders. Your Portable Text is stored as structured JSON, and the components tell the library how to turn that JSON into HTML.
Hope this clears things up! Let me know if you hit any snags.
Show original thread13 replies
Sanity – Build the way you think, not the way your CMS thinks
Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.