I built this blog using Gatsby and Contentful, a combination that I love. Contentful is one of my favorite CMS to work with. And Gatsby makes building blogs so easy.
When I first built the site, I had the challenge of migrating over content. For some of the pages that have images embedded in the text, I had a problem.
Gatsby is great at rendering images. To do this, you need to leverage the Gatsby Image Plugin. For most of the images on the site, that is no problem. For images in Rich Text, or any references, Contentful returns custom objects.
At the time I didn't think of a reasonable way to translate that to something I can pass into the Gatsby Image plugin. For Markdown, I was at least able add srcset HTML markup. Although this was super inefficient.
For Rich Text, I was forced to just process the asset and as a standard img
tag. While I had access to the fuild data for the image, I would end up using the file url data as an img
for the [BLOCKS.EMBEDDED_ASSET]
render option.
While it worked to a degree, how I was rendering the image was inefficient.
Solution
After my most recent Gatsby upgrade, I decided to revisit the issue.
I started out by including gatsbyImageData in my GraphQL fragment for Rich Text. If you don't know what I mean by "fragment", check out the blog post I did on organizing your GraphQL calls.
fragment RichTextBlock on ContentfulRichText {
id
content {
raw
references {
gatsbyImageData(layout: CONSTRAINED, quality: 80, formats: [WEBP, AUTO], placeholder: BLURRED)
contentful_id
title
}
}
sys {
contentType {
sys {
id
}
}
}
}
This gets us all the images that are inside the rich text. The only problem is that they are all together in a single collection. There is no context as to where in the rich text they appear. So we need a way to determine where in the rich text to render them.
What I ended up doing, was creating a dictionary. It's key was the Contentful Id, the value an object containing the gatsbyImageData and title. The title I leverage for alt text.
const richTextImages = {};
class RichText extends React.Component {
render() {
this.props.content.content.references.map(reference => (
richTextImages[reference.contentful_id] = {"image": reference.gatsbyImageData, "alt": reference.title}
))
return (
<div>
{ documentToReactComponents(JSON.parse(this.props.content.content.raw), options) }
</div>
)
}
}
I stored this in a variable in my component called richTextImages
. I used that same variable in the options I configured for Contentful's documentToReactComponents
. In the option for [BLOCKS.EMBEDDED_ASSET]
, I had access to the Contentful Id of the asset rendering. From there, its a matter of pulling the data from the dictionary, and leveraging Gatsby Image.
const Bold = ({ children }) => <b>{children}</b>;
const Text = ({ children }) => <p className="align-center">{children}</p>;
const richTextImages = {};
const options = {
renderMark: {
[MARKS.BOLD]: text => <Bold>{text}</Bold>,
},
renderNode: {
[BLOCKS.PARAGRAPH]: (node, children) => <Text>{children}</Text>,
[BLOCKS.EMBEDDED_ASSET]: (node, children) => {
// render the EMBEDDED_ASSET as you need
const imageData = richTextImages[node.data.target.sys.id];
const image = getImage(imageData.image)
return (
<div className="align-center">
<GatsbyImage image={image} alt={imageData.alt}/>
</div>
);
},
},
};
And that's it! I can now add images from Contentful in Rich Text with the Gatsby Image plugin.
While there might be other solutions out there, this one worked for me. Since my blog posts still use Markdown, they do not leverage this fix. But you can check it out on one of my other pages.
There are a few items that I want to explore further. Expanding the Asset model in Contentful and storing Gatsby Image properties is one.
Please let me know if you came up other options. I would love to see how others got this working.
Thanks to #WOCinTech for the teaser photo used in this post!
Top comments (0)