How to render rich text from blocks in Gatsby?

23 replies
Last updated: Jan 27, 2026
Does anyone have a good step-by-step guide on how to render rich text from blocks on gatsby?
AI Update

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/react

Basic 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 BlogPost

Handling 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

  1. 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.

  2. Use resolveReferences to automatically populate referenced documents within your Portable Text. Without this, you'll just get reference IDs instead of the actual content.

  3. The maxDepth parameter controls how deeply nested references are resolved (typically set to 5 is sufficient for most use cases).

  4. 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.

  5. 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.

Show original thread
23 replies

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.

Was this answer helpful?