How to retrieve all fields of a Netlify CMS Relation in Gatsby
Tags: netlifycms, gatsby, jamstack
Published: 2020-02-27 21:42:10

*Illustration by Katerina Limpitsouni [undraw.co/illustrations](undraw.co)*

Recently I started migrating an older Drupal based blog to a combination of Gatsby and Netlify CMS ([source](https://github.com/patricksevat/de-europeanen-jamstack)), 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](https://dev.to/patricksevat/a-beginners-guide-to-understanding-your-gatsby-starter-template-4f1f) first if you do not feel comfortable yet.

## Setting up a relation in Netlify CMS

If you have read [my previous blog](https://dev.to/patricksevat/a-beginners-guide-to-setting-up-netlify-cms-343a-temp-slug-6341293), you've set up your content collections and fields by now.

One of the widgets I really like is the [relation widget](https://www.netlifycms.org/docs/widgets/#relation). 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:

```yaml
# ./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
```

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:

```graphql
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
      }
    }
  }
}
```

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:

```jsx
// 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
```

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](https://dev.to/patricksevat/a-beginners-guide-to-understanding-your-gatsby-starter-template-4m6d-temp-slug-3341372) 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:

```jsx
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)
        },
      })
    })
  })
}
```

The `context` field can be used during page creation [to provide extra data](https://www.gatsbyjs.org/docs/recipes/pages-layouts/#directions-3) to the render props of the React Component that is used to generate the page. We can now refactor our Blog template.

```jsx
// 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
```

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.