All skills
sanity-io avatar

/portable-text-serialization

@a11c399 official
by Sanitysanity-io/agent-toolkit187 stars
30

Render and serialize Portable Text to React, Svelte, Vue, Astro, HTML, Markdown, and plain text. Use when implementing Portable Text rendering in any frontend framework, building custom serializers for non-standard block types, converting Portable Text to HTML strings server-side, converting Portable Text to Markdown, extracting plain text from Portable Text, or troubleshooting rendering issues with marks, blocks, lists, or custom types.

Use this Skill: https://skilld.dev/gh/sanity-io/agent-toolkit/portable-text-serialization

This session only. Nothing lands on disk.

rulesreact.md

≈906 tokens on demand. Your agent reads this file only when SKILL.md points to it.

Serialize Portable Text to React

Use @portabletext/react (or re-exported from next-sanity) to render PT in React/Next.js.

npm install @portabletext/react

Basic Usage

import {PortableText} from '@portabletext/react'
// or: import {PortableText} from 'next-sanity'

export function Body({value}: {value: PortableTextBlock[]}) {
  return <PortableText value={value} components={components} />
}

Typed Components Object

import type {PortableTextComponents} from '@portabletext/react'

const components: PortableTextComponents = {
  // Block styles
  block: {
    h1: ({children}) => <h1 className="text-4xl font-bold">{children}</h1>,
    h2: ({children}) => <h2 className="text-3xl font-semibold">{children}</h2>,
    blockquote: ({children}) => (
      <blockquote className="border-l-4 pl-4 italic">{children}</blockquote>
    ),
    // 'normal' is the default paragraph style
  },

  // Custom block types
  types: {
    image: ({value}) => (
      <img
        src={urlFor(value).width(800).url()}
        alt={value.alt || ''}
        loading="lazy"
      />
    ),
    code: ({value}) => (
      <pre data-language={value.language}>
        <code>{value.code}</code>
      </pre>
    ),
  },

  // Marks (decorators + annotations)
  marks: {
    // Decorator
    highlight: ({children}) => (
      <span className="bg-yellow-200">{children}</span>
    ),
    // Annotation
    link: ({children, value}) => {
      const rel = !value?.href?.startsWith('/') ? 'noreferrer noopener' : undefined
      return (
        <a href={value?.href} rel={rel}>
          {children}
        </a>
      )
    },
    internalLink: ({children, value}) => (
      <a href={`/${value?.slug}`}>{children}</a>
    ),
  },

  // Lists
  list: {
    bullet: ({children}) => <ul className="list-disc ml-6">{children}</ul>,
    number: ({children}) => <ol className="list-decimal ml-6">{children}</ol>,
  },
  listItem: {
    bullet: ({children}) => <li>{children}</li>,
  },
}

Props Reference

Component type Props received
block.* {children, value} — value is the full block
types.* {value, isInline} — value is the custom block data
marks.* {children, value, markType, markKey} — value is the markDef data
list.* {children, value}
listItem.* {children, value}

Performance: Stabilize the Components Object

❌ Bad — recreated every render:

function Body({value}) {
  return <PortableText value={value} components={{
    types: {image: ({value}) => <img src={value.url} />}
  }} />
}

✅ Good — defined outside or memoized:

const components: PortableTextComponents = {
  types: {image: ({value}) => <img src={value.url} />}
}

function Body({value}) {
  return <PortableText value={value} components={components} />
}

Plain Text Extraction

import {toPlainText} from '@portabletext/react'

const text = toPlainText(blocks) // for meta descriptions, search indexing

Tailwind Typography Shortcut

For simple blogs without custom blocks, wrap in prose:

<article className="prose lg:prose-xl">
  <PortableText value={value} />
</article>

Reference

Source: SKILL.md on GitHub

No alerts16d4 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The skill provides comprehensive reference guides, code patterns, and best practices for serializing and rendering Portable Text across several frontend frameworks (React, Svelte, Vue, Astro, HTML, Markdown). It implements secure coding recommendations, explicitly advising developers on how to prevent Cross-Site Scripting (XSS) vulnerabilities by sanitizing data and escaping HTML during server-side string generation.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at a11c399. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 weeks ago.

Activeupdated 8 months ago
metadata
{
  "author": "sanity",
  "version": "1.0.0"
}
  • React
  • Vue
  • portable-text
  • sanity
  • svelte
  • astro
  • serialization
  • rendering
  • markdown
  • html

README badge

README badge for sanity-io/agent-toolkit/portable-text-serialization

Render Portable Text to React, Svelte, Vue, Astro, HTML, Markdown, and plain text using the @portabletext library family. Maps PT node types (blocks, marks, custom types) to framework-specific components, with rules and patterns for each target framework and guidance on custom serializers for non-standard block types.

Generated from the current SKILL.md.

Does this skill support rendering Portable Text in React, Vue, Svelte, and Astro?
Yes. The skill covers @portabletext/react, @portabletext/vue, @portabletext/svelte, and astro-portabletext, each following the same component-mapping pattern.
Can I convert Portable Text to HTML or Markdown server-side?
Yes. Use @portabletext/to-html for server-side HTML strings or @portabletext/markdown to convert to Markdown format.
How do I render custom block types like images or code blocks?
Custom types require explicit component mappings in the components object — they will not render by default. Map each custom _type to a renderer that receives the block's value as props.
Does this skill help with plain text extraction?
Yes. Use @portabletext/toolkit to extract plain text from Portable Text content.
What if my framework or target (React Native, Solid, PHP) isn't listed in the rules?
The skill documents the core component-mapping pattern used by all serializers and lists community packages for React Native, Solid, PHP, Python, and other languages on portabletext.org, though dedicated rule files are only provided for React, Vue, Svelte, Astro, HTML, and Markdown.

Generated from the current SKILL.md. These answers refresh after source changes.