Search code examples
reactjsgraphqlgatsbynetlifynetlify-cms

How do I fix "TypeError: childImageSharp is undefined" on NetlifyCMS build?


I'm setting up a simple blog page on an existing gatsby site, I want the person using netlify cms to be able to upload a thumbnail image for the blog post. I've managed to do so and locate it using graphigl

graphiql screenshot

Now I would like to set it to my blog posts page:

import React from "react"
import { Link, graphql, useStaticQuery } from "gatsby"

import Layout from "../components/layout/layout"

const Blog = () => {
  const data = useStaticQuery(graphql`
    query {
      allMarkdownRemark {
        edges {
          node {
            frontmatter {
              title
              date
              thumbnail {
                childImageSharp {
                  fluid(maxWidth: 400) {
                    src
                  }
                }
              }
            }
            fields {
              slug
            }
          }
        }
      }
    }
  `)

  return (
    <>
      <Layout>
        <main className="main">
          <div className="articles">
            <h1 className="articles__title">Articles</h1>
            {data.allMarkdownRemark.edges.map(edge => {
              return (
                <section className="articles__list">
                  <a className="articles__article">
                    <div className="articles__article-artwork">
                      <figure className="articles__article-artwork-wrapper">
                        {edge.node.frontmatter.thumbnail.childSharpImage.fluid.src}
                      </figure>
                    </div>
                    <h2 className="articles__article-title">
                      <Link to={`/blog/${edge.node.fields.slug}`}>
                        {edge.node.frontmatter.title}
                      </Link>
                    </h2>
                    <Link>
                      <p>{edge.node.frontmatter.date}</p>
                    </Link>
                    <div className="articles__article-description">
                      <p></p>
                    </div>
                    <span className="articles__article-more">Read more...</span>
                  </a>
                </section>
              )
            })}
          </div>
        </main>
      </Layout>
    </>
  )
}
export default Blog

Then I get these errors, when redeployed on netlify.

netlify build error

config.yml

backend:
  name: github
  branch: development
  repo: (removed for work reasons)

media_folder: static/img
public_folder: img

collections:
  - name: "blog"
    label: "Blog"
    folder: "src/posts"
    create: true
    slug: "{{slug}}"
    fields:
      - {label: "Layout", name: "layout", widget: "hidden", default: "blog"}
      - {label: "Title", name: "title", widget: "string"}
      - {label: "Publish Date", name: "date", widget: "datetime"}
      - {label: "Body", name: "body", widget: "markdown"}
      - {label: "Image", name: "thumbnail", widget: "image"}

gatsby-node.js

const path = require('path')

module.exports.onCreateNode = ({ node, actions }) => {
    const { createNodeField } = actions

    if (node.internal.type === "MarkdownRemark") {
        const slug = path.basename(node.fileAbsolutePath, '.md')

        createNodeField({
            node,
            name: 'slug',
            value: slug
        })
    }
}

module.exports.createPages = async ({ graphql, actions}) => {
    const { createPage } = actions
    const blogTemplate = path.resolve('./src/templates/blog.js')
    const res = await graphql(`
        query {
            allMarkdownRemark {
                edges {
                    node {
                        fields {
                            slug
                        }
                    }
                }
            }
        }
    `)

    res.data.allMarkdownRemark.edges.forEach((edge) => {
        createPage({
            component: blogTemplate,
            path: `/blog/${edge.node.fields.slug}`,
            context: {
                slug: edge.node.fields.slug
            }
        })
    })
}

gatsby-config.js

module.exports = {
  siteMetadata: {
    title: `removed for work reasons`,
    description: `removed`,
    author: `removed`,
  },
  plugins: [
    `gatsby-plugin-react-helmet`,
    `gatsby-plugin-sass`,
    `gatsby-plugin-remove-serviceworker`,
    `gatsby-transformer-sharp`,
    `gatsby-plugin-sharp`,
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `images`,
        path: `${__dirname}/src/images`,
      },
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `img`,
        path: `${__dirname}/static/img`,
      },
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `src`,
        path: `${__dirname}/src`,
      },
    },
    {
      resolve: `gatsby-plugin-manifest`,
      options: {
        name: `gatsby-starter-default`,
        short_name: `starter`,
        start_url: `/`,
        background_color: `#663399`,
        theme_color: `#663399`,
        display: `minimal-ui`,
      },
    },
    {
      resolve: 'gatsby-plugin-react-svg',
      options: {
        rule: {
          include: /assets/
        }
      }
    },
    {
      resolve: `gatsby-transformer-remark`,
      options: {
        plugins: [
          {
            resolve: `gatsby-remark-images`,
              options: {
                maxWidth: 590,
              }
          },
          {
            resolve: `gatsby-plugin-netlify-cms-paths`,
            options: {
              cmsConfig: `/static/admin/config.yml`
            }
          }
        ]
      }
    },
    // this (optional) plugin enables Progressive Web App + Offline functionality
    // To learn more, visit: https://gatsby.dev/offline
    // `gatsby-plugin-offline`,
    `gatsby-plugin-netlify-cms`,
    `gatsby-plugin-netlify`,
  ],
}

I honestly thought that adding a blog page to my existing gatsby site using netlify cms would be a breeze but it has been one of the most difficult things I've attempted.

Any help is very much appreciated.

Thanks


Solution

  • There are a few things odd to me.

    • Your query is not working (so, breaking the code) because it can't find your image. Change your config.yml media paths to:

        media_folder: static/img
        public_folder: /img
      

      Note the slash (/) in public_folder path.

      This is because it's a relative path and must start with a slash. From Netlify docs (bolded the slash part):

    Public Folder

    This setting is required.

    The public_folder option specifies the folder path where the files uploaded by the media library will be accessed, relative to the base of the built site. For fields controlled by [file] or [image] widgets, the value of the field is generated by prepending this path to the filename of the selected file. Defaults to the value of media_folder, with an opening / if one is not already included.

       public_folder: "/images/uploads"
    

    Based on the settings above, if a user used an image widget field called avatar to upload and select an image called philosoraptor.png, the image would be saved to the repository at /static/img/philosoraptor.png, and the avatar field for the file would be set to /img/philosoraptor.png.

    Your media_folder looks good.

    • The way you are rendering the image inside the <figure> tag. Following your approach, it will render a string with the path of the image, however, you are using the sharp from gatsby-image but you are not using it. I would recommend, among a few trials, the following:

      <figure>
         <Img fluid={edges.node.frontmatter.thumbnail.childImageSharp.fluid}>
      </figure>
      

      Following the gatsby-image approach, you should also use a query fragment like:

         const data = useStaticQuery(graphql`
        query {
          allMarkdownRemark {
            edges {
              node {
                frontmatter {
                  title
                  date
                  thumbnail {
                    childImageSharp {
                      fluid(maxWidth: 400) {
                        ...GatsbyImageSharpFluid
                      }
                    }
                  }
                }
                fields {
                  slug
                }
              }
            }
          }
        }
      `)
      

      Note the ...GatsbyImageSharpFluid fragment to fetch all needed data to use gatsby-image.

    • You are using a staticQuery but you don't need it, since all your data comes from a CMS. You should use a page/template query to improve the performance but this will change your page structure.

      The "standard" way of creating dynamic pages in Gatsby is, using gatsby-node.js to use createPage API, pass the needed data to the template (normally an id or slug) and use that unique data to retrieve the blog/post information.

      You are passing the slug via context but you are never using it:

        context: {
          slug: edge.node.fields.slug
        }
      

      In addition, you are loopìng through all articles with the static query again (allMarkdownRemark) what doesn't make sense and it's a waste of time and performance.

      Your Blog template should look like:

        import React from 'react'
        import { graphql } from 'gatsby'
      
        const Blog = ({data}) => {
          return (
            <div>
             Blog title is: {data.markdownRemark.frontmatter.title}
           </div>
         )
      }
      
      export const query = graphql`
         query BlogQuery($slug: String!) {
        query {
          markdownRemark(fields: { slug: { eq: $slug }}) {
                html
                frontmatter {
                  title
                  date
                  thumbnail {
                    childImageSharp {
                      fluid(maxWidth: 400) {
                        ...GatsbyImageSharpFluid
                      }
                    }
                  }
                }
                fields {
                  slug
                }
              }
           }
         }
      `
      
      export default Blog
      

      Note that you are passing the slug ($slug) as a required parameter (String!), so it can't be null to a page query. Afterward, you are filtering the markdown nodes (markdownRemark) to get the one that matches the context that you are passing in the gatsby-node.js file. In other words, in that context, you have the data for each post.

      Also notice that you may need to change the query in order to match your data structure, I've posted it from scratch without knowing your fields. Use the localhost:8000/___graphql (GraphQL playground) to check it. Your fragments won't work in there since it's a limitation of the GraphQL but it will work on your code, so avoid its usage there but keep it in your code.