Tips for using block-content-to-react in a Gatsby app with GraphQL data

13 replies
Last updated: Apr 24, 2020
Are there any videos on how to use the block-content-to-react (https://github.com/sanity-io/block-content-to-react/blob/master/README.md ) inside a gatsby app where I’m already getting the data via gatsby graphql api already?
AI Update

I searched for specific video tutorials on using block-content-to-react with Gatsby, but unfortunately couldn't find dedicated video content on this exact topic. While "Learn With Jason" has done Sanity episodes, I wasn't able to locate a specific Gatsby + Portable Text video tutorial.

However, since you already have your data coming through Gatsby's GraphQL API, I can help guide you through the implementation!

Important Note on block-content-to-react

The block-content-to-react package you linked is now considered legacy. The modern, recommended approach is to use @portabletext/react instead, which offers better performance and more flexibility.

Using Portable Text with Gatsby

Since you're already getting data via Gatsby's GraphQL API, here's how to render it:

npm install @portabletext/react

Then in your Gatsby component:

import { PortableText } from '@portabletext/react'

function BlogPost({ data }) {
  return (
    <article>
      <h1>{data.sanityPost.title}</h1>
      <PortableText value={data.sanityPost._rawBody} />
    </article>
  )
}

export const query = graphql`
  query($slug: String!) {
    sanityPost(slug: { current: { eq: $slug } }) {
      title
      _rawBody
    }
  }
`

Key point: Use _rawBody (or whatever your field name is with the _raw prefix) in your GraphQL query to get the raw Portable Text data structure that the renderer expects.

If you still want to use the legacy block-content-to-react:

npm install @sanity/block-content-to-react
import BlockContent from '@sanity/block-content-to-react'

function BlogPost({ data }) {
  return (
    <article>
      <h1>{data.sanityPost.title}</h1>
      <BlockContent 
        blocks={data.sanityPost._rawBody}
        projectId="your-project-id"
        dataset="your-dataset"
      />
    </article>
  )
}

Custom Components

Both packages support custom serializers/components for rendering custom blocks, marks, and types:

const components = {
  types: {
    image: ({value}) => <img src={value.imageUrl} />,
  },
  marks: {
    link: ({children, value}) => <a href={value.href}>{children}</a>,
  },
}

<PortableText value={data._rawBody} components={components} />

Learning Resources

While I couldn't find the specific video you're looking for, these resources might help:

The core concepts are the same across React frameworks—you're rendering Portable Text blocks that come through as JSON from your GraphQL query. The main thing to remember is to use the _raw prefix in your Gatsby queries!

Hey Saia, I don’t know about any good videos but I can give you some tips.Remember that you cannot (you can with some hacks, but I don’t know how) run GROQ queries in Gatsby, you have to use GraphQL; Use graphiql to create your queries

Then install the block content component as instructed in the repo.

Import it with a const

import BlockContent from "@sanity/block-content-to-react"
Then run it with your data fields, you usually want the fields starting with _raw for block content

<BlockContent blocks={blocks} />

If you want to do things like add CSS classes to your block content data you’ll need to use a serializer.

Here is one that I used recently, note that this is a full component. You can also just define the serializer and pass it to block content like this

<BlockContent blocks={blocks} serializers={someSerializer} />

My component that serializes looks like this


import BaseBlockContent from "@sanity/block-content-to-react"
import React from "react"
import { Link } from "gatsby"
const serializers = {
  types: {
    block(props) {
      switch (props.node.style) {
        case "h1":
          return <h1 className="text-center">{props.children}</h1>
        case "h2":
          return <h2 className="text-center">{props.children}</h2>
        case "h3":
          return <h3 className="text-center">{props.children}</h3>
        case "h4":
          return <h4 className="text-center">{props.children}</h4>
        case "blockquote":
          return <blockquote>{props.children}</blockquote>
        default:
          return <p className="inspirationText">{props.children}</p>
      }
    },
  },
  marks: {
    internalLink: ({ mark, children }) => {
      const { slug = {} } = mark
      const href = `/${slug.current}`
      return <Link to={href}>{children}</Link>
    },
    externalLink: ({ mark, children }) => {
      const { blank, href } = mark
      return blank ? (
        <a href={href} target="_blank" rel="noopener noreferrer">
          {children}
        </a>
      ) : (
        <a href={href}>{children}</a>
      )
    },
  },
}
const BlockContent = ({ blocks }) => (
  <BaseBlockContent blocks={blocks} serializers={serializers} />
)
export default BlockContent
I just import it in my files and run it, I don’t have to pass serializers to it as it already has one. But I do have to pass blocks
In the file where I want to render the rich text data, I have my graphql query like so:
export const query = graphql`
  query($slug: String!) {
    sanityProject(slug: { current: { eq: $slug } }) {
      title
      author {
        name
      }
      slug {
        current
      }
      
      _rawDescription
      image {
        asset {
          fluid {
            ...GatsbySanityImageFluid
          }
        }
      }
    }
  }
`
then I have my component, like so:
const PostTemplate = ({ data: { sanityProject: post } }) => {
  // const htmlDescription = post.description.split("\n").join("<br />")
  console.log("post", post)
  return (
    <>
      <Header />
      <Layout>
        <h1>{post.title}</h1>
        <p
          css={css`
            font-size: 0.75rem;
          `}
        >
          Posted by {post.author.name}
        </p>
        <br />
        {post.image && <Img fluid={post.image.asset.fluid} alt={post.title} />}
        <br />
        {/* {post._rawDescription.map(content => (
          <pre>{JSON.stringify(content, null, 2)}</pre>
        ))} */}

        <ReadLink to="/">&larr; back to all posts</ReadLink>
      </Layout>
    </>
  )
}
export default PostTemplate
What’s the recommended way to rendering my data from sanity with the rich text styles here ^ in my Gatsby app?
Why do you do a
{post._rawDescription.map(content => (
          <pre>{JSON.stringify(content, null, 2)}</pre>
the _raw elements are great for block content, import the block content like I showed in my earlier code (const BlockContent…)
then run &lt;BlockContet blocks={post._rawDescription} /&gt;

Note that if you have any custom types you may need a serializer
user C
is spot on ... just for some extra context, below is how i'm running my PortableText via serializers
query:

fragment SingleArticleFragment on SanityArticle {
    title
    slug {
      current
    }
    ...
    excerpt
    _rawBodyCopy(resolveReferences: { maxDepth: 10 })
    ...
    }
  }
Single page template:

{_rawBodyCopy && <PortableText _blocks_={_rawBodyCopy} />}

PortableText.js component:

import React from 'react'
import BlockContent from '@sanity/block-content-to-react'
import serializers from './Serializers'

const PortableText = ({ blocks }) => (
  <BlockContent blocks={blocks} serializers={serializers} />
)

export default PortableText
Serializer.js

import InlineImage from './InlineImage'

const serializers = {
  types: {
    inlineImage: InlineImage,
  }
}

export default serializers 
also, here's another serializers i have in different project .. the rest of the items are basically the same.
This one just has more components in the PortableText that I can insert during authoring.


import Image from './Image'
import VideoBlock from './Video'
import VideoEmbedBlock from './VideoEmbedBlock'
import GalleryBlock from './GalleryBlock'
import MapBlock from './MapBlock'
import InstagramBlock from './InstagramBlock'

const serializers = {
  types: {
    imageFull: Image,
    video: VideoBlock,
    gallery: GalleryBlock,
    googleMyMap: MapBlock,
    instagramPost: InstagramBlock,
    videoEmbed: VideoEmbedBlock
  }
}

export default serializers
hope this helps
anyone have any idea how
serializers.container
works? I need some specific wrapping container but docs don't really make it clear how to do it 😕
const serializers = {
  container: YourReactElementHere,
}
looks like this works, now to find out how to add props to it
const serializers = {
  container: ({ children }) => (
    <Stack direction="vertical" gap={3}>
      {children}
    </Stack>
  ),
}
sorted
👌
user Y
i think it makes sense for this to be in the docs? Want me to add a PR?
sure! You’re thinking the README.md for the library?
Yep pretty much. Maybe it should be in all the READMEs for the other libs as well actually 🤔

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?