Portable Text
Global helper
This module defines a global <SanityContent> component that can turn portable text into HTML. It is a lightweight functional component without an instance.
As of v2, <SanityContent> uses @portabletext/vue for rendering portable text. This means features and properties available to @portabletext/vue also work with <SanityContent>. Please refer to their Usage guide for advanced configuration options.
<SanityContent> v2 components. Refer to the following upgrade guide:- To reflect
@portabletext/vue's props,blocks→valueandserializers→componentsattribute name changes have been made. The property types remain the same. - Custom components now receive their data nested within a
props.valueobject. When defining components, you need to extract your props from this structure using object spreading:{...props.value}. This applies to all component types (blocks, marks, styles).
Example
<template>
<SanityContent :value="content" />
</template>
Image handling
The <SanityContent> component automatically handles Sanity images using the <SanityImage> component, including using NuxtImg if installed, for optimization and transformation support. Any props you want to pass to Sanity image can be added as attributes to the image block in Sanity Studio. At this moment it is not set up to handle image hotspot and crop.
To override you can pass an custom image component as shown below.
Example with custom components
<template>
<SanityContent :value="content" :components="components" />
</template>
<script setup>
import { defineAsyncComponent, h, resolveComponent } from 'vue'
import CustomBlockComponent from '~/components/CustomBlockComponent.vue'
const components = {
types: {
// This is how to access a component registered by `@nuxt/components`
lazyRegisteredComponent: props => h(resolveComponent('LazyCustomSerializer'), {
...props.value,
}),
// A directly imported component
importedComponent: props => h(CustomBlockComponent, {
...props.value,
}),
// Example of a more complex async component
dynamicComponent: props => h(defineAsyncComponent({
loadingComponent: () => 'Loading...',
loader: () => import('~/other/component.vue'),
}), {
...props.value,
}),
// You can override the default image component if needed
image: props => h('CustomImageComponent', {
...props.value,
}),
// Example of handling caption and attribution in a custom component
imageWithCaption: props => {
const { asset, caption, attribution, crop, hotspot } = props.value
return h('div', { class: 'custom-image-wrapper' }, [
h('img', { src: `https://cdn.sanity.io/images/.../${asset._ref}` }),
caption && h('p', { class: 'caption' }, caption),
attribution && h('p', { class: 'attribution' }, attribution)
])
},
},
marks: {
// Custom marks handling
internalLink: props => h('a', { href: props.value.href }, props.text)
}
}
</script>
Image Block Structure
The automatic image handling works with the standard Sanity portable text image block structure:
{
"_type": "image",
"asset": {
"_type": "reference",
"_ref": "image-61991cfbe9182124c18ee1829c07910faadd100e-2048x1366-png"
},
"caption": "This is the caption (ignored by default component)",
"attribution": "Public domain (ignored by default component)",
"crop": {
"top": 0.028131868131868132,
"bottom": 0.15003663003663004,
"left": 0.01875,
"right": 0.009375000000000022
},
"hotspot": {
"x": 0.812500000000001,
"y": 0.27963369963369955,
"height": 0.3248351648351647,
"width": 0.28124999999999994
}
}
The component automatically extracts the _ref from the asset object and passes it to the SanityImage component along with any crop and hotspot data for proper image transformation. Caption and attribution data are ignored by the default image component.
Disabling Default Image Handling
If you want to handle images yourself or disable the automatic image handling entirely, you can use the disableDefaultImageComponent prop:
<template>
<!-- Disable automatic image handling -->
<SanityContent
:value="content"
:disableDefaultImageComponent="true"
/>
<!-- Or provide your own image component -->
<SanityContent
:value="content"
:disableDefaultImageComponent="true"
:components="{
types: {
image: props => h('MyCustomImage', {
assetId: props.value.asset._ref,
caption: props.value.caption
})
}
}"
/>
</template>
When disableDefaultImageComponent is set to true, the component will not automatically handle image blocks. If you don't provide your own image component in the components.types.image prop, PortableText will show a warning about the missing component.
<MySanityContent>) which wraps SanityContent with your default components. By creating ~/components/MySanityContent.vue you should be able to use this everywhere in your app without importing it.Advanced Props
The SanityContent component accepts all props from @portabletext/vue:
<template>
<SanityContent
:value="content"
:components="components"
:onMissingComponent="handleMissingComponent"
:listNestingMode="'html'"
/>
</template>
<script setup>
const handleMissingComponent = (message, options) => {
console.warn(`Missing component: ${options.type} (${options.nodeType})`)
}
</script>