3.1 KiB
3.1 KiB
title, description, tags
| title | description | tags | |||||
|---|---|---|---|---|---|---|---|
| Serialize Portable Text to HTML | Convert Portable Text to HTML strings server-side using @portabletext/to-html |
|
Serialize Portable Text to HTML
Use @portabletext/to-html for server-side HTML string generation — useful for RSS feeds, emails, static rendering, or any non-framework context.
npm install @portabletext/to-html
Basic Usage
import {toHTML} from '@portabletext/to-html'
const html = toHTML(portableTextBlocks, {components})
⚠️ Security: Escape HTML
Unlike framework renderers, toHTML returns raw strings. You must sanitize output.
Use htm + vhtml for safe templating, or the built-in escapeHTML utility:
import {toHTML, escapeHTML, uriLooksSafe} from '@portabletext/to-html'
import htm from 'htm'
import vhtml from 'vhtml'
const h = htm.bind(vhtml)
Custom Components
Components are functions returning HTML strings:
const components = {
types: {
image: ({value}) => {
return `<figure>
<img src="${escapeHTML(value.url)}" alt="${escapeHTML(value.alt || '')}" />
${value.caption ? `<figcaption>${escapeHTML(value.caption)}</figcaption>` : ''}
</figure>`
},
code: ({value}) => {
return `<pre data-language="${escapeHTML(value.language)}"><code>${escapeHTML(value.code)}</code></pre>`
},
},
marks: {
link: ({children, value}) => {
const href = value?.href || ''
if (!uriLooksSafe(href)) return children
const rel = href.startsWith('/') ? '' : ' rel="noreferrer noopener"'
return `<a href="${escapeHTML(href)}"${rel}>${children}</a>`
},
strong: ({children}) => `<strong>${children}</strong>`,
em: ({children}) => `<em>${children}</em>`,
highlight: ({children}) => `<mark>${children}</mark>`,
},
block: {
h1: ({children}) => `<h1>${children}</h1>`,
h2: ({children}) => `<h2>${children}</h2>`,
blockquote: ({children}) => `<blockquote>${children}</blockquote>`,
normal: ({children}) => `<p>${children}</p>`,
},
list: {
bullet: ({children}) => `<ul>${children}</ul>`,
number: ({children}) => `<ol>${children}</ol>`,
},
listItem: {
bullet: ({children}) => `<li>${children}</li>`,
},
}
With htm/vhtml (Auto-Escaped)
Using htm + vhtml auto-escapes attribute values, preventing XSS from user content that could break out of attributes in raw template literals:
const components = {
types: {
image: ({value}) => h`<img src=${value.url} alt=${value.alt || ''} />`,
},
marks: {
link: ({children, value}) => {
if (!uriLooksSafe(value?.href || '')) return children
return h`<a href=${value.href}>${children}</a>`
},
},
}
Use Cases
| Use Case | Why toHTML |
|---|---|
| RSS/Atom feeds | Need raw HTML string |
| Email templates | No framework runtime |
| Static site generation | Pre-render at build time |
| API responses | Return HTML from endpoints |
| PDF generation | Feed HTML to PDF libraries |