Drew's Ramblings

Bringing Contentful in the mix

2019-06-16

The first version…

The first version of this site followed the Gatsby tutorials fairly closely. The tutorials bring you through building an initial site utilizing the gatsby-starter-hello-world, and overall it went smooth! After learning about what some of the plugins and required files do and how to configure them I decided to deep dive in with my site. I used the gatsby-starter-blog as a jumping-off point and created a Markdown file as my first Blog post (see here). This worked pretty well but I knew that it could be easier and better. While I don’t mind creating and modifying Markdown files, it is not as simple as others who have grown accustomed to WYSIWYG (What you see is what you get) editors.

CMS to the rescue

After looking at several Reddit posts about different solutions. I learned that I wanted to look into a Content Management Solution or CMS for short. A CMS is a system that stores and is also used to create different pieces of content (think blog posts). One of the biggest names of CMS that you have probably heard of is Wordpress it is what is now considered to be a Traditional CMS. In a Traditional CMS there are 3 main parts:

  • a Theme describing how content is to be shown
  • a Database housing all of the different Content
  • a Connector that connects the database to the Theme to pull in all of the Content

While this is in no means a bad setup many of the posts I was reading compelled me to go a slightly different direction. Instead of having all three parts deeply connected I decided to use a Headless CMS where the connector is not included. This allows me to, later on, change out the technologies a bit easier. There is a nice separation of concerns between how I want to get and show my Content and where I create the Content. For my purposes, I chose to use Contentful as it still allows me to still use Markdown for creating my articles (They do now have a Rich Text Editor). But it brings in a handy editor were anyone else not familiar with Markdown to use instead.

Handy Editor

That’s great but how do I set that up?

Alright on to the actual meat and potatoes of this article how is it set up. The first thing that we need to do is create a Contentful account and make our first space. Once this is completed we can then start setting up our Content Model.

Menu

A Content Model is a blueprint for a piece of Content. In my setup I currently have two different Content Models:

Content Models

One is set up for the Blog Posts that you are reading right now. While the other is set up for a combination of the Author of the Blog Post as well Later I want to set it up for User’s to comment on my posts. I still need to do more research into this as I may be thinking about using Contentful incorrectly for this, but rather it is something that should not be done on a static site.

The last step is to fill out all of the fields that we will want for our Blog Post. In my case I have the following setup:

Blog Post Fields

Most of these fields should be fairly self-explanatory with the few oddities being that Author is a reference to a User, CreatedDate is only a Date even though it says Date & time, and that Content while shows as a Long Text it is set up to be styled in Markdown. If you do happen to need more fields they can be added by clicking the “Add Field” button in the sidebar on the right-hand side.

Now all that is left is to fill out some Content by clicking the Content Tab and choosing the Content Model you created.

Updating the Gatsby Code

To begin pulling in the new Content being created in Contentful we need to first install gatsby-source-contentful and since I am using Windows I will also want to use dotenv to hide my API keys as Environment Variables. To install both of these types

npm i gatsby-source-contentful dotenv

into your Terminal.

Updating gatsby-config

From here we need to update our gatsby-config.js file with the following:

// gatsby-config.js
const dotenv = require("dotenv")

if (process.env.NODE_ENV !== "production") {
  dotenv.config()
}

module.exports = {
  siteMetadata: {
    ...
  },
  plugins: [
    ...
    {
      resolve: `gatsby-source-contentful`,
      options: {
        spaceId: process.env.CONTENTFUL_SPACE_ID,
        accessToken: process.env.CONTENTFUL_ACCESS_TOKEN,
        host: `preview.contentful.com`,
      }
    }
  ],
}

The first part:

const dotenv = require("dotenv")

if (process.env.NODE_ENV !== "production") {
  dotenv.config()
}

sets up our dotenv package so that we can later use process.env to access our Contentful Space Id as well as the Acess Token without committing them to our git repo.

To pull our Content from Contentful we set the following values in our plugins:

{
  resolve: `gatsby-source-contentful`,
    options: {
      spaceId: process.env.CONTENTFUL_SPACE_ID,
      accessToken: process.env.CONTENTFUL_ACCESS_TOKEN,
      host: `preview.contentful.com`,
    }
}

This connects to our Contentful Space and allows us to query they data within.

The final step with updating the gatsby-config.js file is to create our .env file:

// .env
CONTENTFUL_SPACE_ID=[SPACE_ID]
CONTENTFUL_ACCESS_TOKEN=[ACCESS_TOKEN]

replacing the values in square brackets with your actual values from Contentful (removing the brackets as well).

Updating gatsby-node

Now that we can query our Contentful Content we want to update our gatsby-node.js file to build our blog posts dynamically. Originally the gatsby-starter-blog was doing this with Markdown files. Below are the changes:

// gatsby-node.js
const path = require(`path`)

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

  const blogPost = path.resolve(`./src/templates/blog-post-contentful.js`)  return graphql(    `      {        allContentfulBlogPost(sort: { fields: createdDate, order: DESC }) {          edges {            node {              slug              title              description {                childMarkdownRemark {                  html                }              }            }          }        }      }    `  ).then(result => {
    if (result.errors) {
      throw result.errors
    }

    // Create blog posts pages.
    const posts = result.data.allContentfulBlogPost.edges
    posts.forEach((post, index) => {
      const previous = index === posts.length - 1 ? null : posts[index + 1].node
      const next = index === 0 ? null : posts[index - 1].node

      createPage({
        path: post.node.slug,        component: blogPost,
        context: {
          slug: post.node.slug,          previous,
          next,
        },
      })
    })

    return null
  })
}

Some of these lines or the query will need to be updated based on how you set up your Content Models in Contentful.

Creating blog-post-contentful.js

Initially, the idea of separating this file from the initial blog-post.js was so that if needed I could display them differently. I still have a bit of learning to do as I kept running into issues until I deleted blog-post.js and am unsure why. but overall blog-post-contentful.js follows the same pattern but just updates everything for my Contentful structure.

// blog-post-contentful.js
import React from "react"
import { Link, graphql } from "gatsby"

import Bio from "../components/bio"
import Layout from "../components/layout"
import SEO from "../components/seo"
import { rhythm, scale } from "../utils/typography"

class BlogPostContentfulTemplate extends React.Component {
  render() {
    const post = this.props.data.contentfulBlogPost    const siteTitle = this.props.data.site.siteMetadata.title
    const { previous, next } = this.props.pageContext

    return (
      <Layout location={this.props.location} title={siteTitle}>
        <SEO title={post.title} description={post.subtitle} />        <h1>{post.title}</h1>        <p
          style={{
            ...scale(-1 / 5),
            display: `block`,
            marginBottom: rhythm(1),
            marginTop: rhythm(-1),
          }}
        >
          {post.createdDdate}        </p>
        <div
          dangerouslySetInnerHTML={{
            __html: post.content.childMarkdownRemark.html,          }}
        />
        <hr
          style={{
            marginBottom: rhythm(1),
          }}
        />
        <Bio />

        <ul
          style={{
            display: `flex`,
            flexWrap: `wrap`,
            justifyContent: `space-between`,
            listStyle: `none`,
            padding: 0,
          }}
        >
          <li>            {previous && (              <Link to={previous.slug} rel="prev">{previous.title}              </Link>            )}          </li>          <li>            {next && (              <Link to={next.slug} rel="next">                {next.title}              </Link>            )}          </li>        </ul>
      </Layout>
    )
  }
}

export default BlogPostContentfulTemplate
export const pageQuery = graphql`
  query BlogPostBySlug($slug: String!) {
    site {
      siteMetadata {
        title
        author
      }
    }
    contentfulBlogPost(slug: { eq: $slug }) {      id      title      content {        childMarkdownRemark {          html          excerpt(pruneLength: 160)        }      }      description {        childMarkdownRemark {          html        }      }    }  }
`

As with gatsby-node.js, you may need to modify the query and fields used based on how your Content Model is set up. One big tip I found while trying to find the data needed was to use the graphQL explorer by going to http://localhost:8000__graphql.

Final Thoughts

Overall I am happy by the changes made as I think that it will speed up creating Content and it taught me more about different CMSs. They can be quite beneficial for allowing non-tech users the ability to create new Content without actually knowing the underline structure or how everything works together. I want to expand my site with the separation of concerns like Headless CMSs provide as it will allow me to change up my system without redoing everything.


Drew Casebeer
Written by Drew Casebeer