Back to all posts

Building a JAMstack Blog: Gatsby and ButterCMS on Netlify

Posted by Nirmalya Ghosh on August 15, 2019

In this tutorial, we will create a lightning fast, static blog application hosted with Netlify using Gatsby on the front-end and ButterCMS to serve up the content. As a result, we leave you with a low cost blog that your content marketing team can update without your help.

jamstack blog

Introduction to JAMstack

JAMstack is a way to create fast and secure sites and dynamic apps with JavaScript, APIs, and Markup served without a web server.

All requests are handled by JavaScript running on the client side. We can use any JavaScript framework or library as a part of JAMstack.

All server-side operations are abstracted into re-usable APIs. These are accessed over HTTPS and can be handled by a 3rd party or a custom built one.

The whole website is just a static HTML which is generated generally during build time from files like Markdown. It can also be generated with the help of a static site generator.

Advantages of JAMstack

The advantages of JAMstack are manifold. They are fast and secure by default. They are hosted over a CDN and accessed over HTTPS. They are inexpensive as there are many hosting providers (Netlify, Now) which provides free hosting for static sites.

Apart from these advantages, JAMstack can also have atomic deploys with automated builds. They also have a much better developer experience. Moreover, everything is in version control which allows tracking issues and rolling back versions easier to handle.

We also don’t necessarily need to write any code to save data on a database. As a result, there is much less dev-ops overhead.

Disadvantages of JAMstack

Compared to the advantages that JAMstack has, the number of disadvantages is quite less. 

Although we can create a site very easily with JAMstack, we do need to have JavaScript knowledge. If we don’t know JavaScript well, working with JAMstack might not be a breeze.

If we are not using a 3rd party CMS provider for our APIs, updating content on our site will still require a developer. It isn’t a good experience for someone who isn’t a developer without a CMS.

A simple workflow using Gatsby and JAMstack

In this section, we will be creating a static blog app which will use Gatsby on the front end and the back end will be powered by ButterCMS API.

In case you don’t have a ButterCMS account, head over to ButterCMS to create a free trial account.

We will need to install the Gatsby CLI globally to create a new Gatsby site. We can install Gatsby CLI globally by using the following command:

npm install -g gatsby-cli

Once that is complete, we can create a new Gatsby site using the following command:

gatsby new gatsby-buttercms-app https://github.com/ghoshnirmalya/gatsby-tailwind-minimal-starter

This will clone the https://github.com/ghoshnirmalya/gatsby-tailwind-minimal-starter Gatsby starter boilerplate and generate a new app for us:

jamstack blog

It will also create the first commit in the new app. Let’s go inside the directory and start our app:

cd gatsby-buttercms-app && yarn dev

It will generate a bunch of configs which are required by Gatsby. At the end, we should be able to see a screen like the following:

undefined

We can now visit our app live at http://localhost:8000/ and the GraphQL endpoint can be accessed at http://localhost:8000/___graphql. If we visit our app at http://localhost:8000/, we can now see the following screen:

jamstack blog

You can check out the commit for the changes.

Our app consists of only static data at the moment. Let’s add Markdown support to our app so that we can write blogs in Markdown and those blogs will be shown on our app. First, we need to stop our server which is running and then add the following Gatsby plugins to our app:

yarn add gatsby-source-filesystem gatsby-transformer-remark

The above command will install gatsby-source-filesystem and gatsby-transformer-remark packages to our app. gatsby-source-filesystem is a source plugin for sourcing data into our Gatsby application from our local filesystem and gatsby-transformer-remark parses Markdown files using Remark.

We will also have to update our gatsby-config.js file to include these packages in the Gatsby plugin system of our app:

{
  resolve: `gatsby-source-filesystem`,
  options: {
    name: `markdown-pages`,
    path: `${__dirname}/content`
  }
},
'gatsby-transformer-remark'

Here, we are letting Gatsby know that all our Markdown content will be present in the content directory. So, let’s create a content directory inside the root directory of our app and add a sample Markdown file post-1.md. It will contain the following:

---
path: '/blogs/post-1'
date: '2019-08-04'
title: 'My first post'
---


Lorem ipsum dolor sit amet.

The permalink of the above blog post will be /blogs/post-1.

We will need to create a new file called gatsby-node.js inside our root directory with the following content:

const path = require('path')

exports.createPages = ({ actions, graphql }) => {
  const { createPage } = actions
  const template = path.resolve('src/templates/blog-post.tsx')

  return graphql(`
    {
      allMarkdownRemark(
        sort: { order: DESC, fields: [frontmatter___date] }
        limit: 1000
      ) {
        edges {
          node {
            frontmatter {
              path
            }
          }
        }
      }
    }
  `).then(result => {
    if (result.errors) {
      return Promise.reject(result.errors)
    }

    return result.data.allMarkdownRemark.edges.forEach(({ node }) => {
      createPage({
        path: node.frontmatter.path,
        component: template,
        context: {
          slug: node.slug,
        },
      })
    })
  })
}

This file is responsible for creating separate pages for each of our blog posts. The permalink of each post is defined in the Markdown file itself.

We just need to create one more file for the blog post template. We can create a new file called blog-post.tsx inside src/templates with the following content:

import * as React from 'react'
import { graphql } from 'gatsby'

import Layout from '../components/layout'

interface BlogPostProps {
  data: {
    markdownRemark: {
      html: string
      frontmatter: {
        title: string
        date: string
      }
    }
  }
}

export default function Template({ data }: BlogPostProps) {
  const { markdownRemark } = data
  const { frontmatter, html } = markdownRemark

  return (
    <Layout>
      <div className="max-w-2xl mx-auto">
        <h1 className="font-bold mb-8 text-4xl">{frontmatter.title}</h1>
        <h2 className="font-semibold mb-8 text-base">Published on {frontmatter.date}</h2>
        <div className="text-xl font-serif leading-relaxed" dangerouslySetInnerHTML={{ __html: html }} />
      </div>
    </Layout>
  )
}

export const pageQuery = graphql`
  query($path: String!) {
    markdownRemark(frontmatter: { path: { eq: $path } }) {
      html
      frontmatter {
        date(formatString: "MMMM DD, YYYY")
        path
        title
      }
    }
  }
`

This file is responsible for creating the layout of the individual blog post pages.

If we restart our server, we can see the new blog post at http://localhost:8000/blogs/post-1:

undefined

You can check out the commit for the changes.

At this moment, in our app, we have support for Markdown. Every time we add a new Markdown file inside our content directory, Gatsby will create a new url for us. We can add a new file called post-2.md inside our content directory with the following content:

---
path: '/blogs/post-2'
date: '2019-08-04'
title: 'My second post'
---

Lorem ipsum dolor sit amet.

If we now visit http://localhost:8000/blogs/post-2, we can view the new blog post.

undefined

You can check out the commit for the changes.

Challenges with Static Markdown Data

Writing blogs with Markdown is pretty easy and it takes very little time to get those changes deployed to our live site. However, someone who isn’t adept in Markdown will need to use a tool to convert his or her document to Markdown.

Apart from that, any change needs to be in version control for automated builds to work. These automated builds are triggered by a change in versioning. Developers are generally quite adept in maintaining versions. However, non-developers will need to use some tool to handle this.

Make sure you receive the latest JAMstack articles straight to your inbox.
    

Using ButterCMS to Solve Static Markdown Data Challenges

The problems with Markdown can be solved if our JAMstack sites are powered by some headless CMS like ButterCMS.

We can just write our data in a WYSIWYG editor and publish those changes live on our website. We don’t have to wait for automated deploys since the data from our site is fetched using ButterCMS APIs. As a result, it becomes very easy and less time-consuming to post updates to our site.

Removing Markdown Support

In this section, we will remove Markdown support in our app because you don’t need this when using ButterCMS.

Let’s start with removing the packages we don’t need. We can remove gatsby-source-filesystem and gatsby-transformer-remark from our package.json file. We also need to remove the plugins from our gatsby-config.js file. We will need to remove the following lines from gatsby-config.js file:

{
  resolve: 'gatsby-source-filesystem',
  options: {
    name: 'content',
    path: `${__dirname}/content`,
  },
},
'gatsby-transformer-remark',

We can also remove our gatsby-node.js and src/templates/blog-post.tsx files as it’s not necessary anymore. Now, only http://localhost:8000/ url will be available. The pages http://localhost:8000/blogs/post-1 and http://localhost:8000/blogs/post-2 won’t be available anymore since we removed them.

Integrating ButterCMS with Gatsby

In this section, we will integrate the ButterCMS API with our Gatsby app.

We will need to install the gatsby-source-buttercms package in our app:

yarn add gatsby-source-buttercms

We will also have to update our gatsby-config.js file to include this package in the Gatsby plugin system of our app:

{
  resolve: 'gatsby-source-buttercms',
  options: {
    authToken: process.env.GATSBY_BUTTER_CMS_API_KEY,
  },
},

We will need to create a new .env.development file in our root directory with the following content:

GATSBY_BUTTER_CMS_API_KEY=your_buttercms_api_key

You can get your ButterCMS API key from your dashboard.

We will also need to add the following to our gatsby-config.js file to add support for environment variables:

require('dotenv').config({
  path: `.env.${process.env.NODE_ENV}`
})

It’s always a good idea not to leak credentials in your code base. Hence, we are using environment variables.

Let’s add a new gatsby-node.js file again with the following content:

const path = require('path')

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

  const template = path.resolve(`src/templates/blog-post.tsx`)

  return graphql(`
    {
      allButterPost {
        edges {
          node {
            slug
          }
        }
      }
    }
  `).then(result => {
    if (result.errors) {
      return Promise.reject(result.errors)
    }

    result.data.allButterPost.edges.forEach(({ node }) => {
      createPage({
        path: `/blogs/${node.slug}`,
        component: template,
        context: {
          slug: node.slug,
        },
      })
    })
  })
}

We will also need a new template for our blog posts. Let’s create a new file blog-post.tsx at src/templates/blog-post.tsx with the following content:

import React from 'react'
import { graphql } from 'gatsby'

import Layout from '../components/layout'

interface BlogPostProps {
  data: {
    butterPost: {
      date: string
      body: string
      title: string
    }
  }
}

export default function Template({ data }: BlogPostProps) {
  const { title, date, body } = data.butterPost

  return (
    <Layout>
      <div className="max-w-2xl mx-auto">
        <h1 className="font-bold mb-8 text-4xl">{title}</h1>
        <h2 className="font-semibold mb-8 text-base">Published on {date}</h2>
        <div className="text-xl font-serif leading-relaxed" dangerouslySetInnerHTML={{ __html: body }} />
      </div>
    </Layout>
  )
}

export const pageQuery = graphql`
  query($slug: String!) {
    butterPost(slug: { eq: $slug }) {
      title
      body
      date
      meta_description
      status
      tags {
        name
      }
    }
  }
`

You can check out the commit for the changes.

Fetching data from ButterCMS

In this section, we will create a new blog post in ButterCMS.

We can create one blog post through our dashboard. Once we write a post, we can publish that by clicking on the Publish button:

undefined

If we hadn’t modified the URL slug of our page manually, ButterCMS will generate one for us and we can view the slug from the SEO tab:

undefined

Now, if we restart the server of our app, we can view the new blog post at http://localhost:8000/blogs/aenean-id-velit-felis:

Deploying to Netlify

In this section, we will deploy our app to Netlify.

Once we have an account at Netlify, we can create a new site from here.

undefined

We can search for your repository which you want to deploy:

undefined

In the next step, we will need to add the GATSBY_BUTTER_CMS_API_KEY environment variable inside Advanced build settings:

undefined


Once the site is deployed, we can view it live at https://gatsby-buttercms-app.netlify.com/. In your case, the url will be different. We can view the url of our site from our project’s overview section:

undefined

We can also view the other blog posts on the live site.

Now, every time we push to the master branch of our Github repository, the changes will be deployed automatically.

Let’s also make all the blog posts appear on our landing page so that we can view all of them from one place.

We just need to replace the contents of src/pages/index.tsx with the following:

import * as React from 'react'
import { graphql, Link } from 'gatsby'

import Layout from '../components/layout'

interface BlogsProps {
  data: {
    allButterPost: {
      edges: [
        {
          node: {
            id: string
            slug: string
            title: string
            meta_description: string
          }
        }
      ]
    }
  }
}

const BlogsPage = ({ data }: BlogsProps) => {
  const posts = data.allButterPost.edges

  return (
    <Layout>
      <div className="flex flex-wrap -mx-4">
        {posts.map(({ node }) => {
          return (
            <div key={node.id} className="w-full sm:w-1/2 md:w-1/3 lg:w-1/4 mb-8 px-4">
              <Link key={node.id} to={`/blogs/${node.slug}`}>
                <div className="max-w-sm rounded overflow-hidden shadow-lg bg-white">
                  <div className="p-8">
                    <div className="font-bold text-xl mb-4">{node.title}</div>
                    <p className="text-gray-700 text-sm">{node.meta_description}</p>
                  </div>
                </div>
              </Link>
            </div>
          )
        })}
      </div>
    </Layout>
  )
}

export default BlogsPage

export const pageQuery = graphql`
  query {
    allButterPost {
      edges {
        node {
          id
          slug
          title
          status
          meta_description
        }
      }
    }
  }
`


This will show all our blog posts on our landing page:

image5.png

You can check out the commit for the changes.

Once we push the changes to Github, we can view it live at https://gatsby-buttercms-app.netlify.com/.

Conclusion

As you can now see that we have made a pretty JAMstack site powered by ButterCMS APIs.

I hope this tutorial helps you in your future projects. Please feel free to share your feedback in the comments section below.

For more JAMstack updates, subscribe to our monthly blog.
    

Related Articles