
Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag storeI'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 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.
{ blocks: text } to toHTML, but the function expects the array directly as the first argumentblocks variable that doesn't exist (blocks.map(...)).replace() is fragile - use the components option insteadThe toHTML function takes two arguments:
componentsHere'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;The components option is how you customize your HTML output. It has three main sections:
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>`,
}
}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>`,
}
}For custom block types (like images) you might add later:
components: {
types: {
image: ({ value }) => {
return `<img src="${value.asset.url}" alt="${value.alt || ''}" />`;
},
}
}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.
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.
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.
Content operations
Content backend


The only platform powering content operations
By Industry


Tecovas strengthens their customer connections
Build and Share

Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag store