Skip to content
Colin Wren
Twitter

Using GraphCMS with Gatsby and Netlify to build a CMS driven static website

JavaScript, Software Development7 min read

car
Photo by Jaimy Willemse on Unsplash

A couple of months ago we finished evaluating the viability of Reciprocal.dev and I started the not so small task of turning what was about 2 months of work done in my spare time into a service that people would want to buy.

We had built a basic landing page during the validation stage using Gatsby & Netlify in order to enable sign ups and this did the job really well.

This approach started to become a bottleneck though as I was tied up working on the app functionality, and with my co-founder not being as technical as myself, our plans of revamping that one page website into a knowledge base for the product required my involvement and these workstreams couldn’t be run in parallel.

I had known that Gatbsy could create pages from GraphQL after using it during my time building JiffyCV to pull data from our Jira into webpages in order to build a basic roadmap tool. So I started looking for a CMS that would work with Gatsby in order to provide content for it to render.

After scoping out a few solutions we finally settled on GraphCMS as it offered a generous free tier and has plugin for Gatsby that makes generating CMS driven pages very easy.

GraphCMS is a ‘headless’ CMS. That is a CMS that doesn’t render a frontend but instead provides the data via an API that a frontend can query in order to render it’s pages.

This is a refreshing change from the old monolithic CMSs like Wordpress that I cut my teeth on, where all the plugins and themes would lead to developing anything new being a right pain. Headless CMSs have a clear separation of concerns and this makes my architectural OCD very happy.

GraphCMS provides its API via GraphQL which is a good fit for the type of data a CMS handles as being able to return a subset of values and use resolvers to transform the data allows the same content to be rendered multiple ways.

Getting started with GraphCMS

Once you’ve registered with GraphCMS you’re able to create projects from a number of templates such as blogs, eCommerce sites and more or you can roll your own with the blank project template.

If you created a blank project or used a template that doesn’t quite meet your means you’ll likely need to use the schema editor to create and amend models used for content.

GraphCMS’s schema editor makes it really easy to model the different objects used within the system and as GraphQL doesn’t employ versioning the schema changes are instantenous, although it’s wise to be aware of breaking changes and look to migrate consumers to use new fields before deprecating and removing existing ones.

The content editor allows you to create instances using the models in the schema in order to build up the content to render on the website. It’s likely that as you start to add content you’ll realise that you need to change the schema to enable what you want so I’d recommend taking an iterative approach instead of trying to force a rigid structure from the outset.

For our website we have a pretty simple schema with the most complex aspect being our Block field, which acts as a union of different content types so we can layout pages in a variety of ways.

Our Schema has the following models:

  • Banner — A horizontal banner with some text and buttons used to break the page up and act as a call to action
  • Hero — A large element at the start of the page that acts like a heading and focal point
  • Footer — A collection of links displayed at the end of every page
  • Header — A collection of top level links displayed at the top of every page
  • PageContent — Long form text without any styling
  • Grid — A means of splitting a list of items into a grid layout, it has properties to define the number of columns for the grid
  • GridItem — An item in the Grid has a smaller set of properties tailored towards the grid layout
  • Page — A page on the website, it has a title, SEO properties and a list of Blocks that are used to render the content to the page

Once we had the schema and the content in place I then used the GraphQL API Playground in GraphCMS to build up a set of queries to return the data, this, similar to how adding content gave me a few ideas on how to improve the schema to make it easier to work with.

Hooking Gatsby up to GraphCMS

As mentioned at the start of the article Gatsby is able to use GraphQL APIs as a data source that it can use to create both pages and content blocks.

In order to use the GraphCMS API you can install the gatsby-source-graphcms plugin and add that to your gatsby-config file with the API endpoint defined in the options:

1{
2 resolve: 'gatsby-source-graphcms',
3 options: {
4 endpoint: 'CONTENT_URL_FROM_GRAPHCMS_SETTINGS'
5 }
6}

You can find the Content URL to add to this config under the API Access section of the project settings. I also had to set up permissions to allow all published content to be publically available in the Public Content API section.

With the plugin installed you can now query GraphCMS from within your Gatsby code to either create new pages by including the below in your gatsby-node file or by performing queries within your components themselves.

1/**
2 * Implement Gatsby's Node APIs in this file.
3 *
4 * See: https://www.gatsbyjs.org/docs/node-apis/
5 */
6const path = require(`path`)
7const MarkdownIt = require('markdown-it')
8const { createRemoteFileNode } = require(`gatsby-source-filesystem`)
9
10const markdown = new MarkdownIt()
11
12exports.createPages = async ({ actions, graphql }) => {
13 const { data } = await graphql(`
14 query PagesQuery {
15 pages: allGraphCmsPage() {
16 nodes {
17 id,
18 slug,
19 }
20 }
21 }
22 `)
23 data.pages.nodes.forEach(page => {
24 actions.createPage({
25 path: page.slug,
26 component: path.resolve(`./src/components/Page.js`),
27 context: {
28 pageId: page.id,
29 },
30 })
31 })
32}
33
34exports.createResolvers = ({
35 actions,
36 cache,
37 createNodeId,
38 createResolvers,
39 store,
40 reporter,
41}) => {
42 const { createNode } = actions
43 createResolvers({
44 GraphCMS_Asset: {
45 imageFile: {
46 type: `File`,
47 resolve(source, args, context, info) {
48 return createRemoteFileNode({
49 url: source.url,
50 store,
51 cache,
52 createNode,
53 createNodeId,
54 reporter,
55 })
56 },
57 },
58 },
59 GraphCMS_Page: {
60 subtitle: {
61 resolve(source, args, context, info) {
62 return markdown.render(source.subtitle)
63 }
64 }
65 }
66 })
67}
Creating pages from the page objects returned from GraphCMS

The queries used with Gatsby are a little bit different than those written in the GraphCMS API Playground as Gatbsy essentially serves the data up via it’s own GraphQL API with a bunch of help queries such as the allGraphCmsPage query used in the example above, this does not exist on the GraphCMS API.

In the code above the GraphCMS API is queried to get the id and slug for all pages available and then a route for each result is created with the Page component used to render the content with the context data being passed to it.

In the Page component you can then further query the GraphQL API to get the content for the page which will be made available to component via a data prop.

1import React from 'react'
2import { graphql } from 'gatsby'
3import Layout from './Layout'
4import SEO from './Seo'
5
6export default function Page({ data: { page }}) {
7 return (
8 <Layout>
9 <SEO data={page.seo}/>
10 <h1>{page.title}</h1>
11 <div dangerouslySetInnerHTML={{ __html: page.subtitle }}></div>
12 </Layout>
13 )
14}
15
16export const query = graphql`
17 query PageQuery($pageId: String!) {
18 page: graphCmsPage(id: { eq: $pageId }) {
19 title
20 subtitle
21 seo {
22 title
23 keywords
24 description
25 noIndex
26 }
27 }
28 }
29`
Querying and rendering page content from GraphCMS

I partially touched on resolvers earlier, but here you can see in the gatbsy-node file we added a resolver for the subtitle attribute on the GraphCMS_Page object that converted the raw Markdown string from the GraphCMS API into HTML.

We can then use that value within the component using dangerouslySetInnerHTML . This ability to transform the data when reading it I think is my favourite feature of GraphQL as it makes the API adaptable for any number of means.

Warning: Remark doesn’t play well with Gatsby at the moment

One problem I encountered during the development of our website was around the Markdown library used in the majority of the documentation, they all used remark to convert the Markdown to HTML.

At the time of writing Gatbsy doesn’t natively support ES modules and it looks like the authors of remark have made a decision to move all their code to ES modules.

This resulted in issues where when I attempted to use require to import the module into my gatsby-node file the ES module would throw an error about not being imported via an import statement.

I attempted a couple of work arounds such as using esm and attempting to port the site over to Typescript before finally giving up and moving to a Markdown library that supported CommonJS.

That library turned out to be markdown-it and it’s worked out pretty well.

Dealing with different schema models in a collection

My GraphCMS schema has a union type in it, this means that a value could be one of a set of types, each with their own disparate set of attributes.

With the potential for completely different objects to be returned under the same attribute on the schema there’s a need to be able to define what values we want from each object and when rendering them a means on how to determine what type of object it is so we can use the correct mapping to render our HTML.

We can use inline fragments in the query to define what values we want to return for each type as shown below:

1function TypeOne({ title, subtitle }) {
2 return (
3 <h1>{title}</h1>
4 <p>{subtitle}</p>
5 )
6}
7
8function TypeTwo({ image, content }) {
9 return (
10 <div>
11 <img src={image} />
12 <p>{content}</p>
13 </div>
14 )
15}
16
17export default function Page({ data: { page }) {
18 const blocks = page.blocks.map((block) => {
19 const Block = block.remoteTypeName === 'One' ? TypeOne : TypeTwo
20 return <Block data={block} />
21 })
22 return (
23 <>
24 <h1>{page.title}</h1>
25 {blocks}
26 </>
27 )
28}
29
30export const query = graphql`
31 query PageQuery($pageId: String!) {
32 page: graphCmsPage(id: { eq: $pageId }) {
33 title
34 blocks {
35 ... on GraphCMS_One {
36 id
37 remoteTypeName
38 title
39 subtitle
40 }
41 ... on GraphCMS_Two {
42 id
43 remoteTypeName
44 image
45 content
46 }
47 }
48 }
49 }
50`
Using the inline fragments to define the fields we want from the different types returned by a union type value

We can then use the remoteTypeName value as a means of determining which component to render the data for the field based on the type.

When building webpages these union types are really useful as it allows you to combine a number of different page sections into a list and generate any number of layouts by just reordering the list and swapping out different blocks.

Schema rebuilds

This flexibility does have one drawback, although this is mostly down to the way that Gatsby maintains it’s own API mapping of the source API from GraphCMS.

I found when iterating over my schema design during development that I would often have to restart the entire Gatsby development server instead of being able to rely on the hot reload functionality that makes Gatsby such fun to work with.

This was because the mapping that Gatsby uses for pulling data from GraphCMS was only updated on the server starting up and not when for instance I added a new value to include in my queries.

Triggering Gatsby builds on content being published

By default, the relationship between Gatsby and GraphCMS is one-sided and static, with Gatsby pulling in data from GraphCMS when the website is built and depending on your set up you could find your content becoming out of date easily.

Fortunately Netlify allows webhooks to trigger builds and GraphCMS has a means to trigger a webhook when content is published so this is relatively easy to fix.

In your Netlify console you can generate a build webhook URL from the Build & Deploy settings page of the Site Settings section for the website, it’s under the Build Hooks heading and once you’ve set up a URL you can use this in GraphCMS to trigger a build.

In GraphCMS you can access the webhooks page by clicking the top most icon on the bottom set of menu items (the icon kind of looks like a fidget spinner) and on that screen you can add a new webhook, you don’t need to fill out all the boxes, just the URL, name and what events you want to trigger a build.

Once you’ve set up the webhook you’ll now have a more dynamic set up which meets my original need to empower my co-founder to be able to make changes to the website without technical knowledge.

Summary

GraphCMS enabled us to move away from a hand-cranked Gatsby site that was causing bottlenecks in our business due to the need for technical need to update the site and gave my non-technical co-founder the means to completely revamp our website to better sell our product.

From a technical perspective it was a nice excuse for me to learn a bit more about GraphQL and it’s inspired me to think about how I could better model the data behind Reciprocal.dev to allow for greater flexibility.