Rendering rich text from blocks on Gatsby using @portabletext package and gatsby-source-sanity plugin
Based on the documentation and GitHub repository for gatsby-source-sanity, here's a comprehensive step-by-step guide for rendering Portable Text (rich text blocks) in Gatsby:
Setting Up Portable Text Rendering in Gatsby
First, install the required package:
npm install @portabletext/reactBasic Implementation
The key is to use the _rawBody field (or whatever your Portable Text field is named) with the resolveReferences option in your GraphQL query. Here's a complete example:
1. Query Your Data
In your page or template component, query the raw Portable Text field:
import React from 'react'
import { graphql } from 'gatsby'
import { PortableText } from '@portabletext/react'
export const query = graphql`
query BlogPostQuery($slug: String!) {
sanityPost(slug: { current: { eq: $slug } }) {
title
_rawBody(resolveReferences: { maxDepth: 5 })
}
}
`Important: Use _rawBody (not just body). The _raw prefix gives you the raw JSON data that @portabletext/react needs. The resolveReferences parameter will automatically replace reference fields with the actual referenced documents.
2. Render the Portable Text
const BlogPost = ({ data }) => {
const { title, _rawBody } = data.sanityPost
return (
<article>
<h1>{title}</h1>
<PortableText value={_rawBody} />
</article>
)
}
export default BlogPostHandling Images in Portable Text
If your Portable Text includes images, you'll want to create custom components. Here's how to handle images with gatsby-plugin-image:
import { GatsbyImage } from 'gatsby-plugin-image'
import { PortableText } from '@portabletext/react'
import { getGatsbyImageData } from 'gatsby-source-sanity'
const sanityConfig = {
projectId: 'your-project-id',
dataset: 'your-dataset'
}
const components = {
types: {
image: ({ value }) => {
const imageData = getGatsbyImageData(
value.asset._ref,
{ maxWidth: 800 },
sanityConfig
)
return <GatsbyImage image={imageData} alt={value.alt || ''} />
}
}
}
const BlogPost = ({ data }) => {
return (
<article>
<h1>{data.sanityPost.title}</h1>
<PortableText
value={data.sanityPost._rawBody}
components={components}
/>
</article>
)
}Alternatively, if you want to query the image data through GraphQL (which can be more efficient), you can query for gatsbyImageData directly:
query BlogPostQuery($slug: String!) {
sanityPost(slug: { current: { eq: $slug } }) {
title
_rawBody(resolveReferences: { maxDepth: 5 })
}
}Then use a custom component that accesses the resolved image data:
const components = {
types: {
image: ({ value }) => {
if (!value?.asset?.gatsbyImageData) return null
return (
<GatsbyImage
image={value.asset.gatsbyImageData}
alt={value.alt || ''}
/>
)
}
}
}Custom Marks and Blocks
You can customize any aspect of the rendering with the components prop:
const components = {
marks: {
link: ({ children, value }) => (
<a href={value.href} target="_blank" rel="noopener noreferrer">
{children}
</a>
),
},
block: {
h2: ({ children }) => <h2 className="my-custom-heading">{children}</h2>,
normal: ({ children }) => <p className="my-paragraph">{children}</p>,
},
}
<PortableText value={_rawBody} components={components} />Key Points to Remember
Always use
_rawfields for Portable Text - these provide the raw JSON structure needed by@portabletext/react. The plugin automatically creates these fields for any array or object type at the root of your documents.Use
resolveReferencesto automatically populate referenced documents within your Portable Text. Without this, you'll just get reference IDs instead of the actual content.The
maxDepthparameter controls how deeply nested references are resolved (typically set to 5 is sufficient for most use cases).Custom components via the
components.typesprop let you control exactly how each block type, mark, or annotation renders. This is essential for images and any custom block types you've defined in your schema.gatsby-source-sanity provides special utilities like
getGatsbyImageData()that work outside of GraphQL queries, perfect for dynamically rendering images from Portable Text content.
This approach gives you full control over your rich text rendering while leveraging Gatsby's GraphQL layer and Sanity's powerful Portable Text specification.
Sanity – Build the way you think, not the way your CMS thinks
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.