Closed codeithuman closed 5 years ago
I have images coming in by putting the following into my plugins[]
in gatsby-config.js
, but it doesn't use gatsby-image
and is not what should be done.
// gatsby-config.js
...
{
resolve: `@contentful/gatsby-transformer-contentful-richtext`,
options: {
renderOptions: {
renderNode: {
[BLOCKS.EMBEDDED_ASSET]: node => {
return `<img src="${node.data.target.fields.file['en-US'].url}" />`
}
},
},
},
},
...
hey @codeithuman,
Unfortunately, that's not possible with the plugin, since it's using html instead.
an alternative is to use a richtext-js transformer like this library and use the gatsby Image
component
I see, that makes sense, hence the name ‘ToHTML’. I will give the ‘to JSX’ library you linked a try.
Thanks so much for the help and quick reply. I really appreciate your time @khaledgarbaya.
@Khaledgarbaya Can we reopen this as a feature request? It doesn't seem impossible for the body { json }
GraphQL query from a content model's rich text to be further transformed within Gatsby's data layer. This isn't a feature to change how it's rendered, rather how the images in the rich text object within the Gatsby data layer are transformed with sharp.
Perhaps this isn't the right repo, but since Contentful is maintaining the Gatsby Source Contentful plugin, I'm not sure the correct place for this feature request.
anyone solved this yet? cant figure how to query the image from the rich text content on contentful
Im rendering it like this (dont mind the squiggy lines)
@jmdmacapagal So the way I got it to work for me was to utilize the option in gatsby-source-contentful
to download assets locally
adding downloadLocal: true
to your config for the contentful source
When enabled you'll be able to query all the images
via allContentfulAsset { edges{ node { } } }
For my purposes i did a query for the post and allContentfulAsset
Then filtered the json data for all nodes with embedded-asset-block
and then used the id to match a node in the images query with the same contentful_id
.
Then I could send that to the gatsby-image
component.
I don't know if that's the most efficient way to do it but it's currently working :)
I got this to work using @AnalogMemory's concept. Here's the code example:
// RichTextRenderer.js
import React from "react";
import { BLOCKS } from "@contentful/rich-text-types";
import { documentToReactComponents } from "@contentful/rich-text-react-renderer";
import Image from "gatsby-image";
import { useContentfulImage } from "../../hooks";
const options = {
renderNode: {
[BLOCKS.EMBEDDED_ASSET]: node => {
const fluid = useContentfulImage(
node.data.target.fields.file["en-US"].url
);
return (
<Image title={node.data.target.fields.title["en-US"]} fluid={fluid} />
);
}
}
};
export default ({ richTextJson }) =>
documentToReactComponents(richTextJson, options);
And the hook that connects the nodeId to the fluid image.
// useContentfulImage.js
import { graphql, useStaticQuery } from "gatsby";
export default assetUrl => {
const { allContentfulAsset } = useStaticQuery(
graphql`
query CONTENTFUL_IMAGE_QUERY {
allContentfulAsset {
nodes {
file {
url
}
fluid(maxWidth: 1050, quality: 85) {
...GatsbyContentfulFluid_withWebp
}
}
}
}
`
);
return allContentfulAsset.nodes.find(n => n.file.url === assetUrl).fluid;
};
Then in your blog post or wherever pass the rich text Json into the RichTextRenderer component <RichTextRenderer richTextJson={bodyTest.json} />
Think this should be re-opened as the feature has not been built
I agree, this is such a standard use case I'm very surprised to find out there isn't a recommended approach for doing this. Building a blog in contentful and having images in the content - I actually expected it to just work. @Khaledgarbaya
const options = {
renderNode: {
// eslint-disable-next-line react/display-name
[BLOCKS.EMBEDDED_ASSET]: (node) => {
console.log(node)
const image = node.data.target.fields.file['en-US']
const width = image.details.image.width
return <Img width={image.details.image.width} fluid={{
aspectRatio: width / image.details.image.height,
src: image.url + '?w=630&q=80',
srcSet: `
${image.url}?w=${width / 4}&&q=80 ${width / 4}w,
${image.url}?w=${width / 2}&&q=80 ${width / 2}w,
${image.url}?w=${width}&&q=80 ${width}w,
${image.url}?w=${width * 1.5}&&q=80 ${width * 1.5}w,
${image.url}?w=1000&&q=80 1000w,
`,
sizes: '(max-width: 630px) 100vw, 630px'
}} />
}
}
}
Here was my solution, I just mashed the url into the format react-image was expecting from the fragment. No idea if this is better or worse than @AnalogMemory 's solution
I decided fixed images were better for my solution, this is what I ended up using.
const options = {
renderNode: {
// eslint-disable-next-line react/display-name
[BLOCKS.EMBEDDED_ASSET]: (node) => {
const image = node.data.target.fields.file['en-US']
let width = image.details.image.width
let height = image.details.image.height
if (width > 630) {
// That's a bit too wide, lets size her down
height = (630 / width) * height
width = 630
}
return <Img fixed={{
width: width,
height: height,
src: `${image.url}?w=${width}&h=${height}q=80&fit=fill`,
srcSet: `
${image.url}?w=${width}&h=${height}&q=80&fit=fill 1x,
${image.url}?w=${Math.round(width * 1.5)}&h=${Math.round(height * 1.5)}&q=80&fit=fill 1.5x,
${image.url}?w=${width * 2}&h=${height * 2}&q=80&fit=fill 2x,
${image.url}?w=${width * 3}&h=${height * 3}&q=80&fit=fill 3x
`
}} />
}
}
}
I've seen similar performance impact to using gatsby-image the regular way. The blog I'm making has a width of 630px, so that's effectively what I'm using as a manual max-width.
Is there any type of update on this?
we were able to solve this recently. using @AnalogMemory 's solution didn't work for us because it was causing a huge json blob to be included in our bundle. this removes the manual process of creating the urls and doesn't require including all of the contentful assets
/* gatsby-node.js */
const { get } = require('lodash')
const getImagesFromRichText = edge =>
get(edge, 'node.body.json.content', []).reduce((acc, c) => {
const url = get(c, 'data.target.fields.file.en.url')
if (c.nodeType == 'embedded-asset-block' && url) {
return [...acc, url]
}
return acc
}, [])
const blogPostData = await graphql(`
query BlogPostData {
allContentfulBlogPost {
edges {
node {
slug
body {
json
}
}
}
}
}
`)
const posts = blogPostData.data.allContentfulBlogPost.edges
posts.forEach((post, index) => {
const images = getImagesFromRichText(post)
createPage({
path: `${pathPrefix}${post.node.slug}/`,
component,
context: {
images,
slug: post.node.slug,
},
})
})
exports.createPagesStatefully = async function({ actions }) {
const { createPage } = actions
await createBlogs({
helpers: { createPage },
path: '/blogs/',
component: require('src/templates/blog'),
})
}
/* src/templates/blog.ts */
import get from 'lodash/get'
import { convertRichText } from 'components/Markup'
export const pageQuery = graphql`
query BlogPostPageQuery($slug: String!, $images: [String!]!) {
contentfulBlogPost(slug: { eq: $slug }) {
slug
body {
json
}
}
allContentfulAsset(filter: { file: { url: { in: $images } } }) {
edges {
node {
fluid(maxWidth: 700, quality: 85) {
...GatsbyContentfulFluid_withWebp
}
}
}
}
}
`
const PostPage: React.SFC<PostPageProps> = props => {
const { data } = props
const imageNodes = data.allContentfulAsset.edges || []
const images = imageNodes.map(edge => edge.node.fluid)
const richText: RichDocument = get(bodyData, 'json')
return (
<div>
{richText &&
convertRichText({
richText,
images,
})}
</div>
)
}
export default PostPage
/* src/components/markup/index.ts */
import { documentToReactComponents } from '@contentful/rich-text-react-renderer'
import get from 'lodash/get'
import Image from 'src/components/Image'
const nodeRenderer = ({ images }) => (
{
renderNode: {
/**
* Render Images
*/
[BLOCKS.EMBEDDED_ASSET]: (node: Block) => {
if (node.data.target) {
const { title, description, file } = get(
node,
'data.target.fields',
{}
)
// image.src has a url param that we need to strip off to match file.url
<Image
src={file.url}
fluid={images.find(
image => image.src.split('?')[0] === file.url
)}
/>
}
},
// ...other rendering functions
},
}
)
export const convertRichText = ({
images,
richText,
}: ConvertRichTextArgs): React.ReactNode =>
documentToReactComponents(
richText,
nodeRenderer({ images })
)
@TomPridham thank you!! This is super helpful and I got it to work!
For anyone looking to do the same, don't just blindly copy paste some of the code like I did.
Don't forget the api returns en-US
so you have to use data.target.fields.file[en-US].url
instead. I guess just make sure you modify the code to match your data shape.
Is there an official fix in the works? It's unclear which hacky solution we're supposed to use:
@codeithuman could you re-open?
Is there an official fix in the works? It's unclear which hacky solution we're supposed to use:
@codeithuman could you re-open?
We're also using the first approach, it looks like this
[BLOCKS.EMBEDDED_ASSET]: node => {
return (
<ContentfulPageImage
title={node.data.target.fields.description['en-GB']}
contentfulId={node.data.target.sys.id.replace(/^c(\d)/, '$1')}
/>
)
},
Our <ContentfulPageImage />
has a useStaticQuery hook and fetches images (containing fluid fields) using a custom GraphQL resolver - to limit results and so there's less to loop through and smaller bundle impact.
I don't believe a better approach is available right now (?)
[edit] - actually @tompridham's approach looks good and even better on the bundle!
Note that @TomPridham 's approach does not cover the recursive case of an entry embed that has an image field you want to use (as opposed to a directly embedded asset).
This deserves its own PR.. Can we please re-open @codeithuman
Hey Folks, Please let not forget that the react renderer needs to work also for plain react app. Also, not everyone is using gatsby-image
.
My suggestion is to get the learnings from this issue and create a helper renderer library that support these cases and people can install seperatly.
// PS: this is an imaginary code
import gatsby-renderer-options from 'gatsby-renderer-options'
//......
documentToReactComponents(
richText,
gatsby-renderer-options
)
@Khaledgarbaya This issue is getting brought up here because the gatsby-source-contentful
is in the Gatsby monorepo. Contentful should take more responsibility for that plugin.
Discussion of this issue on Contentful Slack https://contentful-community.slack.com/archives/CBYTK7T9S/p1586228113005000
Why is this issue closed? Is there a proper way to handle gatsby-image in rich Text?
@Khaledgarbaya would a Contentful gatsby image helper be possible solution?
I guess similar to how sanity-source-plugin.
import Img from 'gatsby-image'
import {getFluidGatsbyImage, getFixedGatsbyImage} from 'gatsby-source-sanity'
const sanityConfig = {projectId: 'abc123', dataset: 'blog'}
const imageAssetId = 'image-488e172a7283400a57e57ffa5762ac3bd837b2ee-4240x2832-jpg'
const fluidProps = getFluidGatsbyImage(imageAssetId, {maxWidth: 1024}, sanityConfig)
<Img fluid={fluidProps} />
This makes it easy to work with gatsby images in Sanity's rich text. Also would be an opt in thing for folks that only use Gatsby and keeps react renderer for everybody.
I think it might work. Exposing resolveFluid would allow us to pass in the asset data from rich text then we'll get that fluidProps back. Will create a prototype this weekend!
Could we start by re-opening the issue?
Alright I got it to work! I think this is the easiest and cleanest way to do it.
So we can essentially use the resolveFluid
and resolveFixed
inside extendNodetypes. I'll write a PR at gatsby's repo if we can have access to those functions.
So all we would have to do is
// image shape is the same as the one returned by your assets in rich text. (Without the localization)
const fluidProps = resolveFluid(image, {})
But here's the gist for those interested in implementing this themselves while waiting for it to merge.
Edit: fixed the gist link
Thanks @daydream05 Will check out this approach and report back!
@daydream05 can you please add the gist, the url returns 404
@daydream05 can you please add the gist, the url returns 404
Fixed it! Sorry bout that.
Here's the link as well:
https://gist.github.com/daydream05/b5befd50f9c9001fb094f331f98a3ec5
@daydream05 this is a great solution, thanks so much!
we should be seeing a fix from the team here: https://github.com/gatsbyjs/gatsby/pull/25249
yahoo!
Since I'm not super advanced, I'm a bit lost here.
What's the proper way to handling this now, given the answer from @chang-ryan ? I am a bit lost following gatsbyjs/gatsby#25249
@Erythros the next version of Contentful can handle it but there’s no timeline yet on when it gets merged. But you can use it now and experiment with it. (I use it for production for 3 client sites and they seem to work fine)
npm i gatsby-source-contentful@next
But if you’re stuck with the current version. You can use the solution I linked.
I highly recommend the next release, which should be merged in due time but is working great for us so far!
I highly recommend the next release, which should be merged in due time but is working great for us so far!
Might as well wait for it. Is there any setup or configuration needed or should work out of the box by just doing this?
{documentToReactComponents(element.json)}
is the next version out? and what is the solution?
Hello ! I have been struggling to get it work so for the people passing by, here is my solution :
My graphql request :
allContentfulBlogPost {
edges {
node {
id
title
content {
raw
references {
... on ContentfulAsset {
contentful_id
gatsbyImageData(layout: FULL_WIDTH, quality: 80, formats: [WEBP, AUTO], placeholder: BLURRED)
description
}
}
}
}
}
}
My Post component :
import React from "react"
import { GatsbyImage, getImage } from "gatsby-plugin-image"
import { documentToReactComponents } from '@contentful/rich-text-react-renderer';
import { BLOCKS } from '@contentful/rich-text-types'
const richTextImages = {};
const options = {
renderNode: {
[BLOCKS.EMBEDDED_ASSET]: node => {
const imageData = richTextImages[node.data.target.sys.id];
const image = getImage(imageData.image)
return <GatsbyImage image={image} alt={imageData.alt}/>
},
},
}
const Post = ({ pageContext, location }) => {
const { post } = pageContext
post.content.references.map(reference => (
richTextImages[reference.contentful_id] = {"image": reference.gatsbyImageData, "alt": reference.description}
))
return (
<>
<h1>{post.title}</h1>
<div>{documentToReactComponents(JSON.parse(post.content.raw), options)}</div>
</>
)
}
export default Post
I hope this will help !
I'm having trouble figuring out how to get images from a Contentful rich text field working with fluid Gatsby images.
Specifically, what does the GraphQL query need to look like? Do I need to edit my
gatsby-node.js
allContentfulArticle
query or do I need to add a query to myarticle-template.js
file?Also, do I need to use
documentToHtmlString
? If so, what element do I need to write a custom renderer for? Will it look similar to the Advanced Example found here?Thanks for any and all help! Let me know what additional information is needed.