5
votes

I am using Gatsby with Netlify CMS. I use gatsby-transformer-sharp for various image manipulations.

In Netlify CMS, if a user deletes an image, the frontmatter value becomes an empty string, e.g:

my-blog-post.md:

---
image: ''
---

This causes gatsby-transformer-sharp to error, when I am querying Graphql:

Error Field "image" must not have a selection since type "String" has no subfields.

This seems to be because Gatsby/Graphql has inferred the image field to be a string.

So I created a schema.md file, so there will always be at least one entry with a valid image in place:

_schema.md:

---
image: /some-dummy-image.jpg
---

Which seems to partially fix the problem - the build only fails occasionally. But it does still fail. I think it must infer its schema from the first markdown file it comes across - sometimes it finds _schema.md first, sometimes it finds my-blog-post.md first.

Has anyone managed to find a solution to this?

2
So far no, but it looks like something is coming -- for now everyone's using a hidden dummy post (that got filtered out in queries)... you can see the discussion hereDerek Nguyen
Thanks - I am already using the 'dummy post' method (I called it _schema.md). The problem is that this sometimes fails. Sometimes Gatsby will incorrectly infer the image field to be a string (because my-blog-post.md contains image: ''). I'm not sure how Gatsby decides which file to use when it infers the schema. I put an underscore in my dummy post filename so it would come first alphabetically when read from the file system, but still it doesn't always work.Rich

2 Answers

4
votes

I managed to solve this in the end. I didn't realise that it was actually easy to directly remove these empty fields from frontmatter, before the schema is inferred.

I made a little custom plugin that would recursively go through frontmatter fields, and any fields that had an empty string are deleted. I also only allowed it to delete fields with a specific name (e.g: image) so it wouldnt cause unexpected changes elsewhere.

src/plugins/remove-empty-fields/gatsby-node.js:

let fieldsToRemove = [];

const deleteFieldsRecursive = (node) => {
  fieldsToRemove.forEach(fieldToRemove => {
    if (node[fieldToRemove] === '') {
      delete node[fieldToRemove];
    }
  });

  if (typeof node === 'object') {
    Object.values(node).forEach(subNode => {
      deleteFieldsRecursive(subNode);
    })
  }
};

exports.onCreateNode = ({ node }, configOptions) => {
  fieldsToRemove = configOptions.fieldsToRemove;

  if (node.internal.type === 'MarkdownRemark') {
    if (!node.frontmatter) {
      return;
    }

    deleteFieldsRecursive(node);

  }
};

Then added this to the plugins list in gatsby-config.js

{
  resolve: 'remove-empty-fields',
  options: {
    fieldsToRemove: [
      'image',
      'bgImage',
    ],
  },
},
4
votes

Update

Since Gatsby@^2.2.0, the best way to handle this error is to use the Gatsby hook createSchemaCustomization. We need to tell Gatsby that the image field will definitely be a file.

exports.createSchemaCustomization = ({ actions }) => {
  actions.createType(`
    type RemarkFrontmatter @infer {
      image: File
    }

    type MarkdownRemark implements Node @infer {
      frontmatter: RemarkFrontmatter
    }
  `)
}

Old answer

I've recently learned that you can modify a markdown's frontmatter directly before its MarkdownRemark node is even created, and come back here to share.

gatsby-transformer-remark use graymatter to parse frontmatter & allows users to pass in a custom parser. Internally, graymatter use js-yaml.

const yaml = require('js-yaml');

const customParser = (str) => {
  const result = yaml.safeLoad(str);

  // remove image (non recursively, as an example)
  const { image, ...withoutImage } = result;

  return withoutImage;
}

And pass it into gatsby-transformer-remark

{
  resolve: `gatsby-transformer-remark`,
  options: {
    engines: {
      yaml: customParser,
    },
}

Syntactically, I prefer the way you do it -- it's clearer when the empty fields are handled by a plugin. Just wanna add another option!