
Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag storeBased 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:
First, install the required package:
npm install @portabletext/reactThe 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:
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.
const BlogPost = ({ data }) => {
const { title, _rawBody } = data.sanityPost
return (
<article>
<h1>{title}</h1>
<PortableText value={_rawBody} />
</article>
)
}
export default BlogPostIf 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 || ''}
/>
)
}
}
}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} />Always use _raw fields 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 resolveReferences to automatically populate referenced documents within your Portable Text. Without this, you'll just get reference IDs instead of the actual content.
The maxDepth parameter controls how deeply nested references are resolved (typically set to 5 is sufficient for most use cases).
Custom components via the components.types prop 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 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.
Content operations
Content backend


The only platform powering content operations
By Industry


Tecovas strengthens their customer connections
Build and Share

Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag store