• NetlifyCMS
  • Gridsome
  • GraphQL

Gridsome + NetlifyCMS Collection Relations

I have been experimenting with NetlifyCMS and Gridsome a lot recently and in general, the two work great together!

Something feels kind of futuristic and forward-thinking when you can manage your content dynamically via a CMS without a "real" backend 🤯. I say "real" in quotes because technically GIT is your backend but let's not ruin the magic ayy?

If you aren't interested in why I am using NetlifyCMS and Gridsome skip to creating relations between types with Gridsome and NetlifyCMS and I'll show you how to create related content such as authors and posts.

Exploring the unknown

Both Gridsome and NetlifyCMS are both in their infancy as far as development tools go, and this means that most of the demos and examples for each of them are relatively simple. You know, "Create a personal blog with Gridsome & NetlifyCMS" that kind of thing.

Well after hearing about NeltifyCMS and playing around with Gridsome for a bit, I got very excited about their combined potential and embarked on a pretty ambitious project using the both of them.

A 100% legit real client of mine (...my mum) needed a site for her holiday cottage business. As sites go it's fairly complex and in short it's basically a smaller Airbnb. To summarise it needed to do the following:

  • Have many different content types (Properties, Locations, Blog, Single Pages etc)
  • Dynamic property search
  • Lots of photo galleries
  • API integration with their booking and payment provider
  • Follow best practices and have good SEO

Question: Could Gridsome + NetlifyCMS achieve the above?

Answer: YES 🥳

The site isn't quite ready to launch but you can see a development version of it here: Rye and Beyond Holiday Cottages

While working on this site I have had to veer off the beaten track and figure out a lot of things for myself. Along the way, I have discovered lots of useful tips and tricks that I plan to share in posts like these.

Anyway, on to the first of these:

Creating relations between types with Gridsome and NetlifyCMS

I am using the Gridsome NetlifyCMS Plugin to easily install NetlifyCMS in your Gridsome site and assuming you already have this plugin installed and set up correctly. If not, there is a great guide here that will help you get started.

One of the first sticking points I hit when using these two tools together was how to create relations between different types. For example, if I had two NetlifyCMS collection types:

  • Blogs posts
  • Authors

I wanted to add an "author" field to the Blog post type.

Before we go any further, the general workflow for adding new types of content with NetlifyCMS and Gridsome is as follows:

  1. Define the new collection types and its fields in the NetlifyCMS config.yml file
  2. Add in some test content via the CMS
  3. Git pull to get the new content locally
  4. Edit your gridsome.config.js to tell it about the new content type and make it available via the Gridsome GraphQL API.

When I first tried these steps one to three worked perfectly. I set up the relations between collections with a NetlifyCMS config similar to below:

# NetlifyCMS config.yml
- name: "blog"
  label: "Blog"
  folder: "content/blog"
  create: true
  slug: "{{year}}-{{month}}-{{day}}-{{slug}}"
  fields:
    - {label: "Title", name: "title", widget: "string"}
    - {label: "Body", name: "body", widget: "markdown"}
    - label: "Author"
      name: "author"
      widget: "relation"
      collection: "authors"
      searchFields: ["name"]
      displayFields: ["name"]
      valueField: "name"

- name: "authors" 
  label: "Authors" 
  folder: "content/authors"
  create: true
  slug: "{{slug}}"
  identifier_field: "name"
  fields:
    - {label: "Name", name: "name", widget: "string"}
    - {label: "Profile", name: "profile", widget: "markdown"}
    - {label: "Profile Image", name: "image", widget: "image"}

After adding the above you will have a nice select field allowing you to choose the author in the CMS.

Screen shot showing a NetlifyCMS relation field

The next step is to mirror this relation in your Gridsome config.

Gridsome source markdown refs

To load the markdown files that NetlifyCMS produces in Gridsome you have to use the Gridsome source markdown plugin.

For each collection in your NetlifyCMS config, you need to register the same collection in the Gridsome config. This tells Gridsome where to look for the files, and what it should call their resulting GraphQL type. Your config should look similar to this:

// gridsome.config.js
module.exports = {
  siteName: 'My awesome site',
  transformers: {
    remark: {
      externalLinksTarget: '_blank',
      externalLinksRel: ['nofollow', 'noopener', 'noreferrer'],
      anchorClassName: 'icon icon-link',
    }
  },
  plugins: [
    {
      use: `gridsome-plugin-netlify-cms`,
      options: {
        htmlPath: `./src/admin/index.html`,
        modulePath: `./src/admin/index.js`,
      }
    },
    {
      use: '@gridsome/source-filesystem',
      options: {
        path: 'content/authors/*.md',
        typeName: 'Author',
      }
    },
    {
      use: '@gridsome/source-filesystem',
      options: {
        path: 'content/blog/*.md',
        typeName: 'BlogPost',
        route: '/blog/:year/:month/:day/:slug',
        refs: {
          author: 'Author',
        }
      }
    },
  ]
};

The key part here is if the refs property in the BlogPost type options. This tells Gridsome that the "author" field on the BlogPost type is a relation to the "Author" type.

Excellent that should work right? Unfortunately not. If you use the GraphQL explorer to query your data you will see that the author field comes back as null.

There is a single comment in the Gridsome source filesystem that explains why this is:

...
// Reference to existing authors by id.
author: 'Author',
...

Gridsome expects that the foreign key used to link types together is an ID field. If you look back at NetlifyCMS config you will notice that neither collection has an ID field and that when we add the author relation "valueField" is set to "name". NetlifyCMS allows you to set the foreign key to any field, and works fine by using the name. Ideally Gridsome source filesystem would allow you to specify a foreign key when defining a reference, but for the time being, we will have to work around the limitation by providing it with an ID field.

Adding Unique IDs to NetlifyCMS Collections

To resolve this issue we need to add an ID field to the Author collection type in NetlifyCMS. Unfortunately, NetlifyCMS doesn't have an ID widget type so we will have to make one. As a side note, it would be great if NetlifyCMS had a config option that would automatically add a unique ID to each entry.

The docs for creating NetlifyCMS widgets can be found here

The first thing we need to do is install a module for creating unique identifiers

npm i uuid

Once that's installed we can go about creating our custom widget. To do this NetlifyCMS requires you to create two separate React components.

Wait a minute... this is a Vue/Gridsome post. How has React snuck its head in here? BECAUSE REACT GETS EVERYWHERE! No, not really. In all seriousness, it's because NetlifyCMS decided that you need to use React to add custom widgets. Us Vue developers would probably prefer a less opinionated way but React's cool too.

Anyway, one of the components handles adding a form element to the CMS content entry pane, and the other handles displaying a preview of the component in the preview pane.

Place the following in src/admin/index.js to create the ID widget. Disclaimer! I'm not a React dev, please forgive me if I have done something horrendous here 😅

import CMS from "netlify-cms"
import uuid from 'uuid/v4';

/**
 * Create the control widget, this will add a form element to the cms UI
 */
const IdControl = window.createClass({  
    getInitialState: function() {    return {};  },
    componentDidMount: function() {
        // If this widget doesn't have an ID yet we create one using the UUID package    
        if (!this.props.value) { 
            this.props.onChange(uuid());    
        } 
    }, 
    handleChange() { 
        this.props.onChange(uuid());
    },  
    render: function() { 
        return window.h('p', null, `${this.props.value}`);
    }
});

/**
 * Create the preview widget, this will display the widgets value in the NetlifyCMS preview pane
 */
const IdPreview = window.createClass({  
    getInitialState: function() { console.log(this.props); return {}; }, 
    render: function() { 
        return window.h('p', null, `ID: ${this.props.value}`);  
    }
});

// Register the widget. This lets NetlifyCMS know about our custom widget
CMS.registerWidget('id', IdControl, IdPreview);

The widget is simple. When it is first initialised it checks to see if it has a value and if it doesn't it creates a new UUID and sets that as it's value.

Now we have a way of autogenerating an id, we need to add the new widget to our collection config, and use that as our reference when creating relations.

collections:
  - name: "blog"
    label: "Blog"
    folder: "content/blog"
    create: true
    slug: "{{year}}-{{month}}-{{day}}-{{slug}}"
    fields:
      - {label: "ID", name: "id", widget: "id"}
      - {label: "Title", name: "title", widget: "string"}
      - {label: "Body", name: "body", widget: "markdown"}
      - label: "Author"
        name: "author"
        widget: "relation"
        collection: "authors"
        searchFields: ["name"]
        displayFields: ["name"]
        valueField: "id"

  - name: "authors" 
    label: "Authors" 
    folder: "content/authors"
    create: true
    slug: "{{slug}}"
    identifier_field: "name"
    fields:
      - {label: "ID", name: "id", widget: "id"}
      - {label: "Name", name: "name", widget: "string"}
      - {label: "Profile", name: "profile", widget: "markdown"}

In the config above we add the ID widget as a new field to the authors collection, then we update blog post's author relation to use the ID as the value field. After doing this, if you have already created some test content you will need to re-save them through the CMS to generate a UUID. If the custom widget has worked correctly you should see something similar to the following in the editor:

Gridsome ID widget

Using the relations

All being well, after pulling your new content changes down locally with a git pull, and restarting Gridsome develop, your relations should no begin to work and you will be able to load the author relation within a post like so:

query Posts {
  posts: allBlogPost {
    edges {
      node { 
        id
        title
        content
        author {
          name
        }
      }
    }
  }
}

And that's it, you can repeat the process to create relations between all types of content and create a really powerful content creation workflow. If you have any questions, or you know a better way of achieving this, please let me know!

17 Responses

12 Likes

5 Reposts

0 Replies

    Contact

    Talk to me

    Feel free to get in touch if you have an interesting project you would like to discuss.