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
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
}
}
}
}
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
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)
},
})
})
})
}
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
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.
Top comments (0)