Blog articles

Build Fast and Elegant Sites with Gatsby, NetlifyCMS and Material-UI

April 11, 2019

This tutorial will show you how to use Gatsby, NetlifyCMS, Netlify, and Material-UI to build out a multi-page site that can be updated with an intuitive CMS. Before diving into the code, I’ll first tell you about the the tools we’ll be working with. 

Gatsby

Gatsby is React-based framework that has recently gained a lot traction and use in production. AirBnb, Flamingo, and Impossible Foods are all companies using Gatsby to build production sites and apps. Gatsby sites get built with React, GraphQL, Gatsby plugins, and some sort of CMS. Gatsby outputs production assets as HTML/CSS/JS files that can be served from a cloud host like AWS S3 or a Google Cloud Storage Bucket.

Material-UI

Material-UI is a React UI framework that implements Google’s Material Design principles in React. It can be added to Gatsby projects in a variety of ways and we’ll see how to use it simply with a plugin.

Netlify/NetlifyCMS

One of the smaller hosting services that works really well with Gatsby is Netlify. Netlify (the company) authored an excellent CMS called NetlifyCMS that makes it really simple to produce/edit/manage content stored in Github. All in all, the combo of GraphQL, Netlify, and NetlifyCMS makes it really simple for developers (who already know React) to build fast websites with an intuitive CMS. NetlifyCMS can be setup so that non-technical users can easily make changes to the content and have Netlify push the changes live. Gone are the days of managing clunky WordPress sites or plugin catastrophes! Also gone are the days of slow loading heavy client side sites (looking at you Soundcloud and Yelp 👀).

Ok, whoah whoah — I’m mostly kidding. The WordPress ecosystem is actually pretty amazing and I use Soundcloud every dang day. What I mean to say is that Gatsby and the entire JAMstack paradigm represent a very exciting evolution in websites that developers will enjoy working on and people will enjoy using.

This tutorial will show you how to setup Gatsby with NetlifyCMS and build out a small demo site (see below). The goal of this tutorial is help give you a better conceptual framework for how Gatsby sites come together. Gatsby already has a very nicely done 8-part tutorial so this tutorial will seek to build upon that and make it even easier to learn Gatsby.

Tutorial Outline:

  • Part 1
    • What We’ll Build
    • About Gatsby (and why try it)
    • Setting up Gatsby
    • A high-level overview of what’s what in Gatsby
    • Understanding Gatsby’s Magic
    • Creating Basic Static Pages with Gatsby
    • Dynamically Creating Pages with gatsby-node.js
    • Creating the About Page
    • Creating the Blog Page and Blog Posts
  • Part 2
    • Setup NetlifyCMS to make editing the content more accessible
    • Create the Courses Page and Individual Courses
    • Add Images and more fields to the Courses and Blogs
    • Style it all with Material-UI

What We’ll Build

This tutorial will be focused on building a very basic clone of the appendTo site (that you’re on right now). This demo site consists of pages for About, Courses, Home, and the Blog. There will also be individual pages for each Course and Blog Post.

The site can be found here: https://appendtostaticstyled.netlify.com/courses

The content in this site lives in a Github repo and can be updated using NetlifyCMS. This gives developers or non-technical users a nice interface for making changes to the content.

About Gatsby (and why try it)

Gatsby is run by VC-backed Berkeley-based Gatsby Inc., which has almost 100 employees (at this time of writing). Many of their founders and employees have ‘making the web fast’ as their Linkedin tagline so it’s fair to say that speed is one Gatsby’s primary selling points. Definitely take a second to try out of some of the sites listed on the Gatsby showcase page. The speed at which an attractive, image-heavy ecommerce site like ShopFlamingo.com loads should inspire you to pick up Gatsby. There’s a pure simple joy in clicking on something and having it load near instantaneously.

The fact that Gatsby is being run as a business and used by large companies should give developers confidence that Gatsby will be around for a while and support will be above average. I find it confidence-inspiring that there are more than 5,000 closed issues on Github.

Furthermore, I received extremely fast help for anything I struggled with while making this post (via Discord, Github, and StackOverflow).

It’s also worth taking a look at some of the blog posts that Gatsby employees have written about their baby. A very interesting one is Making website building fun by founder Kyle Mathews. The whole essay is worth reading and gives you a sense of their founding principles. Mathews describes using the component React-headroom vs. having to implement the same functionality, from scratch, in HTML/CSS/JS (with many more lines). One of the key lines in that essay is this:

What makes technology fun?

This is a complex philosophical question and I’m writing this on a Saturday afternoon so I’ll cheat a bit and just cut the gordian knot by saying “simplicity is fun” and conversely, “complexity is not fun”.

Every one loves new projects. Why? Because they’re simple! We can dive in and experiment and reliably get something done. The more complex the project, the more things seem to break, the more time gets wasted on various yak shaves, and critically, the gap between thinking up an idea and being able to try it grows larger and larger.

Why am I mentioning this bit or the founder at all? Because Gatsby was designed to make building websites more fun. This likely explains the development of their plugin ecosystem and some of the magic and opinions Gatsby has. There’s some upfront difficulty and complexity in learning to play with Gatsby. But once you get it, you’ll probably find it really fun to use.

If you’re still considering other static site options, compare Gatsby with React-static to see how a more manual and less opinionated React-based static site library can look.

And with this background out of the way, let’s start building!

Setting up Gatsby

Gatsby offers a nifty CLI for working with Gatsby projects. As you probably guessed, here’s the terminal command to get it:

npm install -g gatsby-cli

Like many CLIs, typing gatsby –help in the Terminal shows you some of the new commands you can use.

Let’s use gatsby new now to create a new Gatsby project:

gatsby new appendToGatsby

After that build completes, run gatsby develop in the terminal and open http://localhost:8000/ in your browser. Pull the project folder into your text editor of choice.

Take a look at the files and folders generated by the Gatsby CLI. I don’t know about you, but I often feel a bit overwhelmed when I see a whole bunch of new files and folders that a generator has made for me. I sometimes claw through the files frantically trying to figure out what’s what and how it all wires together. To make this easier for you, I’m going to diagram this out.

You should be seeing a folder for components, pages, images and several .js files that live in the src root. Here’s a quick rundown on what they do:

The hardest thing about Gatsby (for me) was all the little nuances and small details that make up its functionality. For example, with this starter project, I found myself looking for the React-router files and a base app.js. Don’t do this! Gatsby projects work differently than normal Create-React-App projects. Let’s create a new page and learn more.

Take what’s in page-2.js and create a file called coolnewpage.js (in that same /pages folder)

Mine looks something like this:

import React from "react"
import { Link } from "gatsby"

import Layout from "../components/layout"
import SEO from "../components/seo"

const CoolNewPage = () => (
 <Layout>
   <SEO title="Cool New Page" />
   <h1>Cool New Page</h1>
   <p>Gatsby is Magical but worth learning</p>
   <Link to="/">Go back to the homepage (index.js file)</Link>
 </Layout>
)

export default CoolNewPage

Then re-run gatsby develop in your terminal, then go to http://localhost:8001/coolnewpage.

Of course this is nothing too special. But consider that you didn’t have to setup the routing or link the components together. Gatsby just detected that a new file was in the pages folder and created the page for it. Try to let some of your React habits go when learning Gatsby (suspend disbelief, if you will). This will make sense with a bit of practice. Let’s now try building something a bit more dynamic.

Building a dynamic About page

Building static pages (by literally placing them in the /pages folder) works fine and would be a good option for really simple ‘brochureware’ sites or a portfolio. But since you’ll likely want to build sites with multiple pages and templates, it makes sense to learn how to use data and templates to build pages dynamically.

We’re next going to build out the About page for this appendTo clone site. Building pages dynamically is quite challenging at first but it’s fairly easy to pick up. Here are the steps:

  1. Create a markdown file (with frontmatter headings) that will hold the page content
  2. Use the Gatsby API methods createPages and onCreateNode to create the page(s) in gatsby-node.js
  3. Create the page template with the appropriate GraphQL query to populate the page with data
  4. Install the appropriate plugins and add them to gatsby-config

It’s hard to understand how these files and APIs connect at first. I’ll walk you through it as slowly and simply as possible.

Step 1: Create the markdown files

Create a markdown file called about.md (that will go in the /pages folder) with the text/info that will go on the about page. Frontmatter (the information between the two triple dashes —) is where you can store fields like the title and the templateKey (which I’ll explain in a bit). Mine looks something like this:

about.md

templateKey: about-page

title: About Page (created with gatsby-node.js)

Step 2: Create the pages using the Gatsby API methods in gatsby-node

We need to now create the pages using Gatsby’s createPages and onCreateNode API methods.

const path = require(`path`)
const { createFilePath } = require('gatsby-source-filesystem')

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

 return graphql(`
   {
     allMarkdownRemark(limit: 1000) {
       edges {
         node {
           id
           fields {
             slug
           }
           frontmatter {
             templateKey
           }
         }
       }
     }
   }
 `).then(result => {
   if (result.errors) {
     result.errors.forEach(e => console.error(e.toString()))
     return Promise.reject(result.errors)
   }


   const posts = result.data.allMarkdownRemark.edges

   posts.forEach(edge => {
     const id = edge.node.id
     createPage({
       path: edge.node.fields.slug,
       tags: edge.node.frontmatter.tags,
       component: path.resolve(
         `src/templates/${String(edge.node.frontmatter.templateKey)}.js`
       ),
       // additional data can be passed via context
       context: {
         id
       },
     })
   })
 })
}

 exports.onCreateNode = ({ node, actions, getNode }) => {
   const { createNodeField } = actions
  
    if (node.internal.type === `MarkdownRemark`) {
     const value = createFilePath({ node, getNode, basePath: `pages` })
     createNodeField({
       name: `slug`,
       node,
       value,
     })
   }
 }

Gatsby’s docs say “We do our best to make Gatsby APIs simple to implement.” I think they do an ok job here but it’s still a lot to grok. It doesn’t make sense for me to completely rehash part 7 of their tutorial where they explain this all in great depth. For this tutorial’s sake, I’ll give you the quick version of what’s going on in the code above.

The onCreateNode part below this is for creating the slugs field so that the page urls work properly. Similar to how that coolnewpage.js created a page at /coolnewpage above, this is doing so manually.

In essence, gatsby-node.js is in charge of querying the markdown files and then creating pages (and slugs) using the markdown data and the page templates. Now we need to create the appropriate page template so that this about page can be created.

Step 3: Create the page templates and their GraphQL queries

Next you need to create a /templates folder and a template for this page. My /templates/about.js page looks like this:

import React from 'react'
import PropTypes from 'prop-types'
import { graphql } from 'gatsby'
import Layout from '../components/layout'

const AboutPage = ({ data }) => {
 const { markdownRemark: post } = data

 return (
   <Layout>
     <h1>{post.frontmatter.title}</h1>   
      <p dangerouslySetInnerHTML={{ __html: post.html }} />      
   </Layout>
 )
}

AboutPage.propTypes = {
 data: PropTypes.object.isRequired,
}

export default AboutPage;

export const aboutPageQuery = graphql`
 query AboutPage($id: String!) {
   markdownRemark(id: { eq: $id }) {
     html
     frontmatter {
       title
     }
   }
 }
`

This looks similar to a normal React component, except for the graphql query at the bottom. This query is using the id passed to the context (on gatsby-node.js) to find the appropriate page (where that id matches). It then retrieves the html and the title from that page (aka, the markdown file). Remember this little bit at the bottom of the previous file?

That same ‘id’ is being used in the query right here to get the right page data.

Step 4: Install Plugins

The final thing we need to do is add the right plugins for us to be able to work with the file system and query markdown files.

Terminal

npm install --save gatsby-source-filesystem

npm install --save gatsby-transformer-remark

Within gatsby-config.js, you need to add these plugins into that [plugins array]:

{
     resolve: `gatsby-source-filesystem`,
     options: {
       name: `src`,
       path: `${__dirname}/src/`,
     },
   },
  ….
   },
...
...
  `gatsby-transformer-remark`,

Then restart the server with gatsby develop and check out the new about page.

The first time doing this can feel a little challenging. There’s a lot of different files being used and it’s pretty easy for things to break. To prevent myself from getting lost in the cognitive overload of Markdown, React, config settings, GraphQL, React context, promises, and nested you-name-its, I came up with mental shorthand for what these files do:

Markdown files: The data that will go into the pages. Easy.

Templates in the /templates folder: React components with GraphQL queries at the bottom used to build the Gatsby static pages.

Gatsby-node.js: Finds my markdown files and creates pages/slugs for them using the right page template.

Gatsby-config.js: Array of plugins and settings I have to touch when installing plugins.

I hope these shorthands help you as you build your mental model of how Gatsby works.

If you need help understanding how to work with GraphQL in Gatsby, Gatsby comes with a built in GraphQL explorer/helper tool called GraphiQLGatsby’s tutorial also has a good write up on how to use it.

Creating the Blog Page and Blog Posts

Now that you’ve made an About page, creating other pages is fairly simple. Since appendTo (and the clone we’re building) is primarily a blog page, we’ll now create some blog posts. This involves creating three new files:

  1. /pages/blog.js (the parent /blog page with a list of the blog posts)
  2. /pages/blogs/blog1.md (an example of the markdown files for the blogs)
  3. /components/BlogItem.js (a small component to show the blog excerpt and title on the /blog page)
  4. /templates/single-blog.js (template for just one blog post)

Here’s how I created the blogs:

Step 1: Create blog.js

import React from "react"
import { StaticQuery, graphql } from "gatsby"

import Layout from "../components/layout"
import SEO from "../components/seo"
import BlogItem from "../components/BlogItem"

class BlogPage extends React.Component {
 render() {
   const { data } = this.props
   const { edges: posts } = data.allMarkdownRemark

   return (
     <Layout>
       <SEO title="Blog" />
       <h1>Blog Page</h1>
       <div>
         {posts &&
           posts.map(({ node: post }) => (
             <BlogItem
               key={post.id}
               post={post.frontmatter}
               slug={post.fields.slug}
               excerpt={post.excerpt}
             />
           ))}
       </div>
     </Layout>
   )
 }
}

export default () => (
 <StaticQuery
   query={graphql`
     query BlogPageQuery {
       allMarkdownRemark(
         sort: { order: DESC, fields: [frontmatter___date] }
         filter: { frontmatter: { templateKey: { eq: "single-blog" } } }
       ) {
         edges {
           node {
             excerpt(pruneLength: 40)
             id
             fields {
               slug
             }
             frontmatter {
               title
               templateKey
               date(formatString: "MMMM DD, YYYY")
             }
           }
         }
       }
     }
   `}
   render={data => <BlogPage data={data} />}
 />
)

This page looks similar to our about page. It is a React component with a GraphQL query at the bottom. This Static Query is a Gatsby component that can be used anywhere to query for data. In this case, it is querying for markdown files that have a templateKey of ‘single-blog’ in the frontmatter header. Let’s make these markdown files now.

Step 2: Create markdown blog files

Here’s an example blog I created and filled with hipster ispum. Notice the templateKey line at the top there. That’s what the query above (in my /blog.js file is looking for).

---

templateKey: single-blog

title: blog3

date: 2019-04-10T16:43:29.834Z

description: so stoked on blog3

---

astropub, small batch godard kickstarter sustainable shoreditch raw denim affogato twee. Disrupt normcore lumbersexual, craft beer aesthetic iPhone chambray irony glossier vinyl skateboard tbh fanny pack. Banh mi sartorial hot chicken semiotics roof party PBR&B whatever brunch, kombucha XOXO tumblr helvetica skateboard. Church-key chillwav

Now we need to make the BlogItem.js component that /blog.js can render this data into.

Step 3: Create BlogItem.js

BlogItem.js

import React from "react"
import { Link } from "gatsby"

function BlogItem(props) {
 const { post, slug, excerpt } = props

 return (
   <div>
     <div>
       <Link to={slug}>
         <h1>{post.title}</h1>
       </Link>
       <h3>{post.date}</h3>
       <p>{excerpt}</p>
     </div>
   </div>
 )
}

export default BlogItem

This is a fairly standard React component that uses the Gatsby Link component for linking to other pages, then restart the development server.

Step 4: Create the single blog.js template

import React from 'react'
import PropTypes from 'prop-types'
import { graphql } from 'gatsby'
import Layout from '../components/layout'

const BlogPage = ({ data }) => {
  const { markdownRemark: post } = data

  return (
    <Layout>
      <p>{post.frontmatter.title}</p>
       <p> {post.html}  </p>
      />
    </Layout>
  )
}

BlogPage.propTypes = {
  data: PropTypes.object.isRequired,
}

export default BlogPage

export const BlogPageQuery = graphql`
  query BlogPage($id: String!) {
    markdownRemark(id: { eq: $id }) {
      html
      frontmatter {
        title
      }
    }
  }

To review what’s happening here, the main /blog page is querying for blog markdown files (e.g. blog1.md), then passing this data to itself and rendering a list of blogs using the BlogItem components.

This post covered most of the basics of setting up Gatsby and creating pages manually and automatically using gatsby-node.js. The following post will explore the following:

  • Creating the Courses Page and Individual Courses
  • Installing NetlifyCMS
  • Styling it all with Material-UI