Rendering rich text from blocks on Gatsby using @portabletext package and gatsby-source-sanity plugin

23 replies
Last updated: Sep 7, 2022
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.

There isn't a specific guide for this, unfortunately. However, you will be using the
@portabletext
package here . That readme contains a lot of information about rendering your blocks.
yeah…I’m new to react so it reads like a bunch of gibberish to me. I got that package installed with no idea how to use it.
If you're new to React, I'd suggest reading through the main concepts documentation to get a feel for how it works.
yeah, i occasionally end up there from google searches and it reads like the plumbus bit from rick and morty https://www.youtube.com/watch?v=eMJk4y9NGvE&amp;ab_channel=AdultSwim
it will just take some digging on my part, but I actually write down step by step guides for myself so I’ll be sure to share
Yeah, I can see it, haha! If you do happen to create a guide we'd love to see it on the exchange . I also remembered there's this really brief section on portable text to React in the documentation (though it probably doesn't make it much clearer than the other stuff I shared 😅 )
thanks, i got the text to render with the defaults but now im contending with the images. i know this is the latest and greatest but i sure miss chucking some yml in a settings doc and calling it a day
Since you're using Gatsby, one thing you may be missing when it comes to images is this bit . Images in documents are references to assets, so by adding this you can get the actual image.
i’ll try that thanks. I was attempting to replicate this fella’s method, but no dice. video for context: https://youtu.be/6OCji3Eyib4?t=1490
ah so i actually have that plugin already, but the issue is getting the images to work when theyre within the blocks
I get this object:
{
              "_key": "183fcbdcd3f5",
              "_type": "image",
              "asset": {
                "_ref": "image-4b6600a50705e57a7d2a56924cbe7500420ed936-1450x1411-png",
                "_type": "reference"
              }
            }
so I imagine I can do something with that _ref but still no clue on how to parse it
Are you using the
_raw
field for your portable text? Here's an example from that repo you shared. It lets you expand the asset reference.
yup, so im getting the _raw through graphql
hmm, weird!
i’ve accepted all javascript is inherently weird, i just need to find each of the methods that work
i know i need to format the image src into a way that gatsbyimage will accept it
the method he used apparently works but it seems they put their configs in a separate sheet then apply the to the getImage call, but it’s not gatsby’s getImage. it’s from sanity/asset-utils
so…..i went and did their set up and it works, but the expected behavior would be I can source the gatsbyImageData from the asset in the block just like i did for an image content type
instead I had to build that weird workaround
im looking in the issues for the gatsby source sanity package that you linked to earlier and I found this https://github.com/sanity-io/gatsby-source-sanity/issues/143#issuecomment-1007819828
i opened an issue for the gatsby-source-sanity plugin to track https://github.com/sanity-io/gatsby-source-sanity/issues/174
user M
Knut helped me out with a much simpler solution. It’s built into the gatsby-source-sanity plugin so we’re all set

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?