How to retrieve all fields of a Netlify CMS Relation in Gatsby

patricksevat

Patrick Sevat

Posted on February 27, 2020

How to retrieve all fields of a Netlify CMS Relation in Gatsby

Illustration by Katerina Limpitsouni undraw.co/illustrations

Recently I started migrating an older Drupal based blog to a combination of Gatsby and Netlify CMS (source), both of which I had no prior experience with. In this blog series I'll talk you through the experiences, hurdles and solutions. In part 4 I'll explain Netlify CMS relations and how to make sure you can retrieve all fields of the linked relation.

ā— This blogs assumes basic understanding of Gatsby and GraphQL queries, read this blog first if you do not feel comfortable yet.

Setting up a relation in Netlify CMS

If you have read my previous blog, you've set up your content collections and fields by now.

One of the widgets I really like is the relation widget. This allows you to link two seperate entities. If you are familiar with SQL it's a bit like a join. In this blog I'll link my blogs collection with my authors collection.

The configuration is rather simple:

# ./static/admin/config.yml
collections:
  - name: "blog"
    label: "Blog"
    folder: "src/pages/blog"
    fields:
      - {label: "Template Key", name: "templateKey", widget: "hidden", default: "blog-post"}
      - {label: "Title", name: "title", widget: "string"}
      - {label: "Body", name: "body", widget: "markdown"}
      - label: "Author"
        name: "author"
        widget: relation
        collection: "author"
        displayFields: ["name"]
        searchFields: ["name"]
        valueField: "name"
     # other fields omitted for brevity
  - name: "author"
    label: "Authors"
    folder: "src/authors"
    identifier_field: "name"
    fields:
      - {label: "Template Key", name: "templateKey", widget: "hidden", default: "author"}
      - {label: "Name", name: "name", widget: string}
      - {label: "Job title", name: "job_title", widget: string}
      - {label: "Profile picture", name: "profile_picture", widget: image, allow_multiple: false}
     # other fields omitted for brevity
Enter fullscreen mode Exit fullscreen mode

Everywhere you query your blogs using GraphQL in Gatsby you will now receive a field with the author's name. But what if you do not only need the name, but also want to display the author's job title and profile picture? Well... that doesn't work out of the box.

Retrieving all fields of a relation

On my blog pages I want to show all fields relating to its author, so I had to work around this limitation of Netlify CMS. In this section I'll show two approaches to make this work. If you know a better way, let me know in the comments!

Populating browser side

The first, more naive route I took was to enhance the graphql query on each page where I also retrieved blogs, by quering the allMarkdownRemark collection again (under the alias authors) to retrieve all authors.

A shortened version of that query:

query BlogPost {
  allMarkdownRemark(id: { eq: $id }) {
    html
    frontmatter {
      title
      author
      # other fields omitted for brevity
    }
  }
  authors: allMarkdownRemark(filter: {frontmatter: {templateKey: {eq: "author"}}}) {
    nodes {
      frontmatter {
        name
        job_title
        profile_picture
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Now that all the authors are available you can retrieve the author of the blog in the browser. This could be done during render of your component:

// src/templates/blog.js
const BlogPost = (props) => {
  const post = props.data.markdownRemark;
  // ā— Not very efficient: providing all authors then looking one up. 
  const author = props.data.authors.nodes
    .find(author => author.frontmatter.name === post.frontmatter.author);

  return (
    <div>
      <h1>{ post.frontmatter.title }</h1>
      <AuthorComponent {...author} />
      <div dangerouslySetInnerHTML={{ __html: post.html }} />
    </div>
  )
};

export default BlogPost
Enter fullscreen mode Exit fullscreen mode

This author retrieval could be optimized by utilizing a [useMemo](https://reactjs.org/docs/hooks-reference.html#usememo) hook.
However, this approach has still several downsides:

  • this goes against the GraphQL principle of querying only what you need
  • all the unnecessary data is added to your javascript chunks, bloating your file size
  • if you have many authors or finding even more relations in the browser, this will cause a performance penalty sooner or later.

Populating server side (using gatsby-node)

A better solution would be to enrich the blog posts during server-side generation. This would increase build time slightly on during page generation at the advantage of better performance on the browser side. A trade-off I'm happy to make!

The gist of this approach is to understand that within Netlify CMS all content types are saved in Markdown. As I explained in a previous blog page creation is done is done in gatsby-node.js. In that file, allMarkdownRemark content is retrieved. The emphasis here is on all content.

To create blogs we filter on a certain templateKey, but that also means that with the right query we can make all authors available during page creation time:

exports.createPages = ({ actions, graphql }) => {
  const { createPage } = actions

  return graphql(`
    {
      allMarkdownRemark(limit: 1000) {
        edges {
          node {
            # used to retrieve individual blog data in page queries
            id
            frontmatter {
              templateKey
              # the author fields in Blog that represents a relation
              author
              # field on the Author content type
              name 
              # field on the Author content type
              job_title
              # field on the Author content type
              profile_picture 
            }
          }
        }
      }
    }
  `).then(result => {
    const posts = result.data.allMarkdownRemark.edges;

    const blogs = posts.filter(post => post.node.frontmatter.templateKey === 'blog-post');
    const authors = posts.filter(post => post.node.frontmatter.templateKey === 'author');

    posts.forEach(edge => {
      const id = edge.node.id
      createPage({
        path: edge.node.fields.slug,
        tags: edge.node.frontmatter.tags,
        component: path.resolve('src/templates/blog.js'),
        // additional data can be passed via context
        context: {
          id,
          author: authors.find((author) => author.node.frontmatter.name === edge.node.frontmatter.author)
        },
      })
    })
  })
}
Enter fullscreen mode Exit fullscreen mode

The context field can be used during page creation to provide extra data to the render props of the React Component that is used to generate the page. We can now refactor our Blog template.

// src/templates/blog.js
const BlogPost = (props) => {
  const post = props.data.markdownRemark;
  const author = props.pageContext.author.node;

  return (
    <div>
      <h1>{ post.frontmatter.title }</h1>
      <AuthorComponent {...author} />
      <div dangerouslySetInnerHTML={{ __html: post.html }} />
    </div>
  )
};

export default BlogPost
Enter fullscreen mode Exit fullscreen mode

That's it! If you know a different or more efficient way of retrieving full relations, drop your knowledge in the comments!


This blog is part of a series on how I migrated away from a self-hosted Drupal blog to a modern JAM stack with Gatsby and Netlify CMS.

šŸ’– šŸ’Ŗ šŸ™… šŸš©
patricksevat
Patrick Sevat

Posted on February 27, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related