add skills
Build and Deploy / build-and-deploy (push) Successful in 2m53s

This commit is contained in:
2026-05-18 08:39:42 +02:00
parent 92a80e8759
commit 57af0b8386
53 changed files with 10190 additions and 0 deletions
@@ -0,0 +1,142 @@
---
title: Serialize Portable Text to React
description: Render Portable Text in React and Next.js using @portabletext/react
tags: [portable-text, react, nextjs, serialization, rendering]
---
# Serialize Portable Text to React
Use `@portabletext/react` (or re-exported from `next-sanity`) to render PT in React/Next.js.
```bash
npm install @portabletext/react
```
## Basic Usage
```tsx
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
```tsx
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:
```tsx
function Body({value}) {
return <PortableText value={value} components={{
types: {image: ({value}) => <img src={value.url} />}
}} />
}
```
**Good** — defined outside or memoized:
```tsx
const components: PortableTextComponents = {
types: {image: ({value}) => <img src={value.url} />}
}
function Body({value}) {
return <PortableText value={value} components={components} />
}
```
## Plain Text Extraction
```tsx
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`:
```tsx
<article className="prose lg:prose-xl">
<PortableText value={value} />
</article>
```
## Reference
- [@portabletext/react](https://github.com/portabletext/react-portabletext)
- [Sanity docs: Presenting Portable Text](https://www.sanity.io/docs/presenting-block-text)