Search code examples
graphqlgatsbycontentfulrichtext

Can't properly render rich text field from Contentful headless cms using Gatsby.js


So, I am building a website for a local business where they can add previous jobs(its a renovation type of business) using contentful cms (sort of a blog of previous jobs).

The problem I have is where I need to display content of each job that is defined in contentful as rich text field.

I will show you my Project component(which is a template for creating pages through gatsby create pages api) and here it is:

    import React from 'react';
    import { graphql } from 'gatsby';
    import { documentToReactComponents } from "@contentful/rich-text-react-renderer"
    import { BLOCKS } from "@contentful/rich-text-types"
    import Layout from '../components/layout';
    
    
    const Project = ({data}) => {
        const RichContent = JSON.parse(data.contentfulProject.projectContent.raw);
        const options = {
            renderNode: {
                [BLOCKS.HEADING_1]: (node, children) => <h1>{children}</h1>,
                // [BLOCKS.EMBEDDED_ASSET]:  <img src={`https:${data.contentfulProject.projectContent.references.fluid.src}`}/>,
            },
        renderMark: {},
        }
        console.log(data)
        return (
            <Layout>
                
                {documentToReactComponents(RichContent, options)}
            </Layout>
        )
    }
    
    export default Project;
    
    export const query = graphql`
        query($id: String!) {
            contentfulProject(id: {eq: $id}) {
                    title
                    projectContent  {
                        raw
                        references {
                                fluid {
                                        src
                                        
                                    }
                            }
                        }
                    }
                }
    `

So actually I managed to successfully render text fields, but when I uncomment [BLOCKS.EMBEDED_ASSET] the page breaks due to lack of data. Images are not displaying.

My guess is that I can't access json field when querying for contentfull data, I get only raw field back.

Please help :D


Managed to solve the problem, here is the code for working component that properly displays images as well:

import React from 'react'
import { graphql } from 'gatsby';
import { renderRichText } from "gatsby-source-contentful/rich-text"
import { BLOCKS } from "@contentful/rich-text-types"

import Layout from '../components/layout'


function ProjectTemplate({ data }) {
  const {projectContent} = data.contentfulProject
  console.log(projectContent)

  const options = {
    renderMark: {},
    renderNode: {
      [BLOCKS.PARAGRAPH]: (node, children) => <p>{children}</p>,
      [BLOCKS.EMBEDDED_ASSET]: node => {
        return (
            <img src={`https:${node.data.target.fixed.src}`}/>
        )
      },
    },
  }

  return (
    <div>
      <Layout>
      {renderRichText(projectContent, options)}
      </Layout>
    </div>
  )
}

export default ProjectTemplate

export const query = graphql`
  query($id: String!) {
    contentfulProject(id: {eq: $id}) {
                contentful_id
                title
                slug
              projectContent {           
                raw
                references {
                  ... on ContentfulAsset {
                    contentful_id
                    __typename
                    fixed(width: 1600) {
                      width
                      height
                      src
                      srcSet
                    }
                  }
                }
              }
            }
          }
`

Solution

  • Use some structure like:

    import { BLOCKS, MARKS } from "@contentful/rich-text-types"
    import { renderRichText } from "gatsby-source-contentful/rich-text"
    ​
    const Bold = ({ children }) => <span className="bold">{children}</span>
    const Text = ({ children }) => <p className="align-center">{children}</p>
    ​
    const options = {
      renderMark: {
        [MARKS.BOLD]: text => <Bold>{text}</Bold>,
      },
      renderNode: {
        [BLOCKS.PARAGRAPH]: (node, children) => <Text>{children}</Text>,
        [BLOCKS.EMBEDDED_ASSET]: node => {
          return (
            <>
              <h2>Embedded Asset</h2>
              <pre>
                <code>{JSON.stringify(node, null, 2)}</code>
              </pre>
            </>
          )
        },
      },
    }
    ​
    renderRichText(node.bodyRichText, options)
    

    Each type of field has its own renderNode, which gives you a lot of flexibility on how do you want to render and style your data with your own components.

    With BLOCKS.EMBEDDED_ASSET your code wasn't returning anything, with the snippet above, you can customize the output, returning an empty object if needed to bypass it.