blog.jakoblind.no

How to create a fast and beautiful blog with Gatsby

The hottest thing right now is to use a static site generator tool to create your blog. This way you only serve pure HTML/CSS/Javascript without any server. This makes the site blazing fast.

My favorite tool for this is Gatsby. It has tons of optimizations out-of-the-box, and it has a very active community.

For devs like you and me, it makes lots of sense to create your blog with a static site generator because you have complete control of your blog. Gatsby is built on React which means you can code React components when you want to extend your blog.

This article will take you through creating a complete blog in Gatsby. The blog you’ll create is going to be very similar to the blog you are reading on right now (blog.jakoblind.no) because I’m using almost the exact same setup. It will not just be a copy-paste tutorial, but I will also teach you the very basics of how Gatsby is built up. How does that GraphQl thing work? How does the plugin system work? Etc. But we’ll start from the beginning creating your blog from scratch.

In this post, you’ll learn the following

Lots of the code in this guide has been heavily inspired (stolen) from the Gatsby docs.

Create your new fresh Gatsby blog

Let’s jump straight into creating your new blog with Gatsby!

Gatsby has templates that give you boilerplate code to get you started quickly. I suggest you use the gatsby-starter-blog starter template. It got some nice stuff such as code for markdown parsing. We need markdown support because we will write our blog posts in markdown. It also has some nice typography using typography.js and code to handle SEO with meta tags.

First, you need to install gatsby globally (you can skip this step if you already have it installed on your machine):

npm install -g gatsby-cli

Now you can create your project using the gatsby-starter-blog template. Use the following command and replace my-cool-blog with whatever you want to call your project:

gatsby new my-cool-blog https://github.com/gatsbyjs/gatsby-starter-blog

A new folder my-cool-blog, or whatever name you specified, is created which contains your new cool blog. Cd into the folder and type ls to get an overview of what’s there.

jlind@localhost ~/d/my-cool-blog> ls
content/  gatsby-browser.js  gatsby-config.js  gatsby-node.js  LICENSE  node_modules/  package.json  package-lock.json  README.md  src/  static/  yarn.lock

A complete project has been generated for you with config files, source code files, etc. To run your new blog on localhost run this command:

npm start

Then go to http://localhost:8000/ and check out your new blog.

gatsby-starter-blog

As you can see there are some sample blog posts that you can click to get a feel how things look. There’s also a header with Kyle Mathews bio and his profile pic. Let’s change that because this is no longer his blog, but yours :)

Let’s change the bio

Open up the the gatsby-config.js in your favorite text editor. At the top of the file, there is a key called siteMetadata. This is the place where you can edit basic information about you such as author and description. It will be picked up automatically everywhere where this info is referenced, such as the first page. On my blog I changed it to this:

siteMetadata: {
    title: `blog.jakoblind.no`,
    author: `Jakob Lind`,
    description: `A blog about React, Redux, Webpack and JavaScript`,
    siteUrl: `http://blog.jakoblind.no/`,
    social: {
      twitter: `karljakoblind`,
    },
  },

This code will pop up on the first page.

Leave the other stuff unchanged for now. We’ll come back to it later.

How to edit a blog post

When creating a blog with the gatsby-starter-blog template like we did, you get some code generated for you. It has created a content directory on the root of your project. In that directory, you have blog folder where all your blog posts are, and an asset folder where the images and other attachments to blog posts are. This is a pretty good setup I think so we’ll keep it. Go ahead and open a post in the blog post folder, for example, content/blog/hello-world/index.md:

---
title: Hello World
date: "2015-05-01T22:12:03.284Z"
---

This is my first post on my new fake blog! How exciting!

I'm sure I'll write a lot more interesting things in the future.

Oh, and here's a great quote from this Wikipedia on
[salted duck eggs](http://en.wikipedia.org/wiki/Salted_duck_egg).

> A salted duck egg is a Chinese preserved food product made by soaking duck
> eggs in brine, or packing each egg in damp, salted charcoal. In Asian
> supermarkets, these eggs are sometimes sold covered in a thick layer of salted
> charcoal paste. The eggs may also be sold with the salted paste removed,
> wrapped in plastic, and vacuum packed. From the salt curing process, the
> salted duck eggs have a briny aroma, a gelatin-like egg white and a
> firm-textured, round yolk that is bright orange-red in color.

If you’re not familiar with markdown, it’s a plain text formatting syntax.

The top of the markdown posts is metadata. In this case, it contains the title and the date. Gatsby will use this information to show the title in a h1 tag and it will show the title in the <title> section. It’ll also use the date to show it and to be able to sort the posts in the post list page to have the latest at the top.

The rest of the markdown file is the actual content of the blog. There is a special syntax for writing, links, images, italic, etc. To learn markdown, mastering markdown by Github is a good resource.

Go ahead and change some of the text of the blog post and see how your changes are reflected in the running app.

The blog posts are in folders. And the folders has an index.md file and some assets.

.
├── hello-world
│   ├── index.md
│   └── salty_egg.jpg
├── hi-folks
│   └── index.md
└── my-second-post
    └── index.md

I prefer to structure my posts in markdown files in the root of the folder. And then have the images and other assets in the content/assets folder. Most of the images are being reused between posts so I prefer to have them all gathered in one place.

.
├── 2-quick-javascript-tips.markdown
├── 3-steps-to-check-if-isomorphicuniversalssr-is-worth-it.markdown
├── 3-ways-to-reduce-webpack-bundle-size.markdown
├── 4-libraries-to-use-in-your-react-app.markdown
├── 4-questions-to-ask-yourself-when-you-get-stuck-with-react.markdown
├── ajax-componentdidmount-vs-componentwillmount.markdown

This works out of the box, you can just create a new post in the content/blog/ folder and it will be picked up automatically by Gatsby.

How Gatsby works with data

When you changed the bio earlier, you changed the gatsby-config.js and the code was automagically updated in the posts. But how does that magic work?

You can think of Gatsby having one large database. And it fills up this database with data sources that you define in gatsby-config.js. In your project, you have two data sources: siteMetadata as we just saw, and gatsby-source-filesystem that is used for reading markdown blog post from your filesystem. This is defined in gatsby-config.js

gatsby sources config

So how does Gatsby access the data from the database? The answer is GraphQL. Open up src/pages/index.js and scroll to the bottom of the file. There you can see how the index page fetches data from the database with a GraphQL query:

export const pageQuery = graphql`
  query {
    site {
      siteMetadata {
        title
      }
    }
    allMarkdownRemark(sort: { fields: [frontmatter___date], order: DESC }) {
      edges {
        node {
          excerpt
          fields {
            slug
          }
          frontmatter {
            date(formatString: "MMMM DD, YYYY")
            title
            description
          }
        }
      }
    }
  }
`

This is an illustration of how the data sources propagate the database, and the GraphQL queries fetch data from the database:

How Gatsby works

If you don’t know GraphQL you don’t have to learn all the in and outs of it, because in Gatsby you will only do queries with it. But it can be nice to know the basics. You can learn more about GraphQL on their official website.

You can access the GraphQL dev interface on http://localhost:8000/___graphql (assuming you are using port 8000). It’s very useful to experience with new queries here because you get an instant response, and you can also explore the format of your data here.

gatsby graphql dev interface
The GraphQL database is only running on localhost. When you create a production build, Gatsby will create JSON files that will contain the data to make the site completely static.

Gatsby uses transformers to understand markdown

Did you look in gatsby-config.js ? Then you noticed there are much more things defined in there than our two sources. Another important type of plugin is transformer. In your config, there are two transformers defined: gatsby-transformer-remark and gatsby-transformer-sharp

It’s the gatsby-transformer-remark that parses your markdown files after gatsby-source-filesystem has read it. This plugin puts an HTML version of the markdown files in the database that you can access with GraphQL.

As you notice our blog lacks some things. It doesn’t have tags, and we don’t have support for pages like an /about page and other static stuff. Let’s create all that stuff now!

Adding tags

Tags are useful for your readers. If they find a good blog post on a topic, for example, Redux, then they are eager to read more posts on the same topic. If your blog supports tags, then they can click your Redux tag that they find on the bottom of your posts:

Redux tag

…and when they do click, they get a list of all posts that covers that topic:

tags list

So let’s implement that for our blog now! We’ll start by defining the tags in our markdown files.

As we saw earlier each post has some metadata at the top of the post:

---
title: How to code Redux and Gatsby
date: "2015-05-01T22:12:03.284Z"
---

content

The text inside --- are metadata. The metadata is a perfect place to add our tags. We’ll support a list of tags, like this:

---
title: How to code Redux and Gatsby
date: "2015-05-01T22:12:03.284Z"
tags:
- redux
- gatsby
---

content

You might need to restart the server for it to pick up these changes properly.

Now, let’s show these tags at the bottom of each blog post. Open up the file src/templates/blog-post.js in your text editor. Each blog posts are rendered with this template. As you can see it’s a React component and at the bottom, there is a Graphql query.

blog-post.js graphql query

This query fetches the content, such as body, title, and date of the blog post. The content is injected into the React component as props.

As you learned earlier all data, such as blog posts, are inside the GraphQL database. When you added the tags to the metadata, Gatsby automatically put it in the database. Now you can fetch these tags in the GraphQL query so that the tags for each post is injected into the React component. Add tags to the GraphQL query in src/templates/blog-post.js file like this (tags is highlighted in blue):

export const pageQuery = graphql`
  query BlogPostBySlug($slug: String!) {
    site {
      siteMetadata {
        title
        author
      }
    }
    markdownRemark(fields: { slug: { eq: $slug } }) {
      id
      excerpt(pruneLength: 160)
      html
      frontmatter {
        title
        date(formatString: "MMMM DD, YYYY")
        description
        tags      }
    }
  }
`

All metadata from each post are put into the frontmatter key in the GraphQL database. That’s why we put the tags in that query. Now you should have tags with all the tags in the React component. Let’s display them. Go to the React component in the file src/templates/blog-post.js and add the tags to a variable:

const tags = post.frontmatter.tags || []

Add that line somewhere in the render function, but before the return statement:

class BlogPostTemplate extends React.Component {
  render() {
    // <--- add it here

    return (

If a post doesn’t have any tags we want it to be an empty list so that we can run .map on it without getting an error. That’s why we add the || [] at the end.

Now let’s add the JSX that will show all tabs at the bottom of each post. We’ll add it in the same file (src/templates/blog-post.js) right after the <hr> tag in the JSX that is returned. This is the JSX you’ll add:

 <div>
  tags:
  <ul
    style={{
      display: `flex`,
      flexWrap: `wrap`,
      justifyContent: `space-around`,
      listStyle: `none`,
    }}
  >
    {tags.map(t => (
      <li key={kebabCase(t)}>
        <Link to={`/tags/${kebabCase(t)}`}>{t}</Link>
      </li>
    ))}
  </ul>
</div>

We use the lodash.kebabcase to convert tags like hello world to hello-world. To use it first install it:

npm i --save lodash.kebabcase

Then import it at the top of the page:

import kebabCase from "lodash.kebabcase"

Now if you open a blog post in your browser, you should see some tags at the bottom of the post. Be sure to pick a post that you actually added some tags to in the markdown header.

The tags are clickable, so if you click the tag redux it has a link to /tags/redux. But that page doesn’t exist yet. Let’s create it!

To create a completely new kind of page we need to do two things:

  1. Add some code in the gatsby-node.js file to create pages
  2. Create a template file that we’ll use from gatsby-node.js code.

Open up gatsby-node.js file in your editor. In here you can see that exports.createPages function. This function creates pages for each individual blog post. It does this by fetching the blog posts with a GraphQL query and then for each post it calls createPage passing in a template ./src/templates/blog-post.js the path to the page and some context variables.

What we are going to do now is to fetch all tags from all blog posts. First, add tags to the GraphQL query.

      {
        allMarkdownRemark(
          sort: { fields: [frontmatter___date], order: DESC }
          limit: 1000
        ) {
          edges {
            node {
              fields {
                slug
              }
              frontmatter {
                title
                tags              }
            }
          }
        }
      }

Next, we’ll write the code that fetches all tags from all blogposts. Put this code in the then block:

let tags = []
// Iterate through each post, putting all found tags into `tags`

posts.forEach(edge => {
  if (edge.node.frontmatter.tags) {
    tags = tags.concat(edge.node.frontmatter.tags)
  }
})
// Eliminate duplicate tags
tags = uniq(tags)

The uniq function removes duplicates, so that if you have redux tag in three posts, it will only show up once in the list. I have implemented uniq like this

var uniq = arrArg => {
  return arrArg.filter((elem, pos, arr) => {
    return arr.indexOf(elem) == pos
  })
}

The next thing we are going to do is to create a page for each tag in our list.

const tagTemplate = path.resolve("src/templates/tags.js")
// Make tag pages
tags.forEach(tag => {
  createPage({
    path: `/tags/${kebabCase(tag)}/`,
    component: tagTemplate,
    context: {
      tag,
    },
  })
})

We also need to import kebab-case at the top of the file. This time with the require syntax:

const kebabCase = require("lodash.kebabcase")

For each call to createPage we create the URL /tags/THETAG/ and we pass in the template tagTemplate which is the file src/templates/tags.js. This is a file that we are going to create now.

import React from "react"

import { Link, graphql } from "gatsby"
import Layout from "../components/layout"
import SEO from "../components/seo"

const Tags = ({ pageContext, data, location }) => {
  const { tag } = pageContext
  const { edges, totalCount } = data.allMarkdownRemark
  const siteTitle = data.site.siteMetadata.title
  const tagHeader = `${totalCount} post${
    totalCount === 1 ? "" : "s"
  } tagged with "${tag}"`

  return (
    <Layout location={location} title={siteTitle}>
      <SEO title={tagHeader} />
      <h1>{tagHeader}</h1>
      <ul>
        {edges.map(({ node }) => {
          const { slug } = node.fields
          const { title } = node.frontmatter
          return (
            <li key={slug}>
              <Link to={slug}>{title}</Link>
            </li>
          )
        })}
      </ul>
      <Link to="/tags">All tags</Link>
    </Layout>
  )
}

export default Tags

export const pageQuery = graphql`
  query($tag: String) {
    site {
      siteMetadata {
        title
      }
    }
    allMarkdownRemark(
      limit: 2000
      sort: { fields: [frontmatter___date], order: DESC }
      filter: { frontmatter: { tags: { in: [$tag] } } }
    ) {
      totalCount
      edges {
        node {
          fields {
            slug
          }
          frontmatter {
            title
          }
        }
      }
    }
  }
`
src/templates/tags.js (the code is mostly copy/pasted from the docs page)

Interesting things to note here is that we filter out only the relevant posts with the filter keyword in the GraphQL query.

   filter: { frontmatter: { tags: { in: [$tag] } } }

Then in the React component we just list all the posts we find that has that tag.

Now there is only one thing left, and that is to create the /tags page which will list all tags aggregated from all blog posts.

all tags

To do this we’ll create a page. Create the file src/pages/tags.js:

import React from "react"
import Layout from "../components/layout"
import { Link, graphql } from "gatsby"
import SEO from "../components/seo"

var kebabCase = require("lodash.kebabcase")

const TagsPage = ({
  data: {
    allMarkdownRemark: { group },
    site: {
      siteMetadata: { title },
    },
  },
  location,
}) => (
  <Layout location={location} title={title}>
    <SEO
      title="all tags"
      keywords={[`blog`, `gatsby`, `javascript`, `react`]}
    />
    <div>
      <h1>Tags</h1>
      <ul>
        {group.map(tag => (
          <li key={tag.fieldValue}>
            <Link to={`/tags/${kebabCase(tag.fieldValue)}/`}>
              {tag.fieldValue} ({tag.totalCount})
            </Link>
          </li>
        ))}
      </ul>
    </div>
  </Layout>
)

export default TagsPage

export const pageQuery = graphql`
  query {
    site {
      siteMetadata {
        title
      }
    }
    allMarkdownRemark(limit: 2000) {
      group(field: frontmatter___tags) {
        fieldValue
        totalCount
      }
    }
  }
`
src/pages/tags.js

We fetch all posts, group them by the tags they have and fetch the tags. Then list them together with the total number of posts they are in. They also link to the tags “details page”.

That was quite a lot of work, but now you are done with your tags system!

Separating pages and blog posts all written in markdown

When you have a blog, you want to create pages such as /about page, or maybe a sales page, etc, and other types of pages that are not really blog posts, but static pages.

You can, of course, create your own pages by creating React components and put them in the /src/pages/ directory. But there too much boilerplate to write React code and GraphQL queries if all you want is to get some text out there quickly. Then markdown is a better option. So let’s implement the possibility to create markdown pages.

You are going to reuse some of the skills we acquired when creating tags page. It’s a similar workflow.

You are going to put the markdown files in the folder /content/pages/. Let’s make Gatsby read markdown files from this folder. We’ll do that by adding the following in the gatsby-config.js file:

    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/content/pages`,
        name: `assets`,
      },
    },

Now Gatsby will read markdown files from this folder and put it in the GraphQL database. The problem is that it will make no difference between blog posts and pages. All your pages will show up in the blog list on the index page. And all pages will have the tags, next/previous posts, etc at the bottom.

To fix this we need to define different templates for blog posts and pages. The way we are going to do that is to define this information in the metadata. We’ll add a new key called templateKey. The contents of this key will describe which template we will use.

Create a new markdown file in the content/pages/ directory where you define the templateKey header as page, like this:

---
title: About Jakob
date: 2019-05-15
templateKey: page
---

Jakob is a programmer!
NOTE: You also need to update all your blog posts and add the templateKey header with the value blog-post

Now we need to use the template that we define in the markdown file. Open up gatsby-node.js in your editor. Fetch the templateKey in the GraphQL query

      {
        allMarkdownRemark(
          sort: { fields: [frontmatter___date], order: DESC }
          limit: 1000
        ) {
          edges {
            node {
              fields {
                slug
              }
              frontmatter {
                title
                tags
                templateKey              }
            }
          }
        }
      }

Next, we’ll use the templateKey when creating the page with the createPage call:

createPage({
  path: post.node.fields.slug,
  component: path.resolve(    `./src/templates/${post.node.frontmatter.templateKey}.js`  ),  context: {
    slug: post.node.fields.slug,
    previous,
    next,
  },
})

The highlighted lines are the lines that we changed in the file.

Now we must create the page template file. Create a new file src/templates/page.js with the following contents:

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

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

class PageTemplate extends React.Component {
  render() {
    const post = this.props.data.markdownRemark
    const siteTitle = this.props.data.site.siteMetadata.title

    return (
      <Layout location={this.props.location} title={siteTitle}>
        <SEO
          title={post.frontmatter.title}
          description={post.frontmatter.description || post.excerpt}
        />
        <h1>{post.frontmatter.title}</h1>
        <div dangerouslySetInnerHTML={{ __html: post.html }} />
      </Layout>
    )
  }
}

export default PageTemplate

export const pageQuery = graphql`
  query PageBySlug($slug: String!) {
    site {
      siteMetadata {
        title
        author
      }
    }
    markdownRemark(fields: { slug: { eq: $slug } }) {
      id
      excerpt(pruneLength: 160)
      html
      frontmatter {
        title
        date(formatString: "MMMM DD, YYYY")
      }
    }
  }
`

Now we use different templates for pages and blog-posts! But if you check the index page, you’ll see that we still see the pages in the listing of blog posts. Let’s filter out the blog posts in the GraphQL query. Open src/pages/index.js add the following filter:

allMarkdownRemark(
  filter: { frontmatter: { templateKey: { eq: "blog-post" } } }  sort: { fields: [frontmatter___date], order: DESC }
)

Now there are no longer any pages in the blog posts listing!

Many times you create a page you want to link to it from the menu. And we don’t even have a menu yet. Lets create a menu and link pages from it.

Add a menu

The “correct” way to create a menu in Gatsby is to define the data as a data source in the gatsby-config.js file, either in the siteMetadata, or any other data source you are using, such as the filesystem or a CMS data source.

But rules are meant to be broken. When you create a blog that is only meant for you and not a large corporate blog with many different editors that must be able to edit the menu in a CMS, then hard-coding the menu is good enough. And it’s much quicker to implement.

Go to src/components/layout.js. This is the file for everything that is common across all your pages in your app.

Create a header Menu component at the top of the file:

const Header = () => {
  return (
    <nav>
      <ol>
        <li>
          <Link to="/">Home</Link>
        </li>
        <li>
          <Link to="/about">About</Link>
        </li>
      </ol>
    </nav>
  )
}

And then in the Layout component that is already defined, use the Header component in the render function:

<Header />

Now the data is there, all we need now is to add some styling. There is no global css file defined out-of-the-box so let’s create it. Create the file src/components/layout.css and add the following:

nav ol {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-around;
    list-style: none;
    color: black;
    margin-left: 1.58rem;
}

You also most import it at the top of layout.js file:

import "./layout.css"

We now have a menu! And if you previously added the page /about, then you will be able to click the About link in the menu to see the about page. Sweet!

Changing fonts

The most important design element in a blog is the text. That’s why it’s so important to have good styling of the text to make the blog look beautiful.

Our blog uses typography.js which is a nice library that does a bunch of magic when it comes to fonts. From the home page:

Typography is a complex system of interrelated styles. 100s of style declarations on dozens of elements must be in harmonious order. Trying one design change can mean making dozens of tedious recalculations and CSS value changes. Creating new Typography themes with CSS feels hard.

Typography.js has a wide selection of themes. Go to the home page of typography.js and select a theme at the top right. When you select a theme you’ll get a live preview instantly. Find a theme you like and remember the name. I personally like the lincoln theme. I’ll use it as an example for the rest of this chapter.

In Gatsby, all themes live as individual NPM modules. Let’s install our NPM theme module:

npm install --save typography-theme-lincoln

Now let’s use it. Go the file src/utils/typography.js and import the theme at the top:

import Lincoln from "typography-theme-lincoln"

And then change this line

const typography = new Typography(Wordpress2016)

With this

const typography = new Typography(Lincoln)

Also, remove all references to the Wordpress2016 variable which was the default theme.

If you need to do some tweaks to the text styles you can do it with the overrideThemStyles function on the theme object. I have the following overrides:

Lincoln.overrideThemeStyles = () => {
  return {
    "nav a": {
      color: "black",
      textDecoration: "none",
      backgroundImage: "none",
      textTransform: "uppercase",
      //  fontFamily: "Montserrat, sans-serif",
    },
  }
}

Syntax highlighting

prism.js is an awesome tool that we’ll use for syntax highlighting. It’s already installed and configured in your project. Try adding some code to one of your blog posts and wrapp the code in ```.

markdown code

When you’ll do, the code will look like this in your blog post:

no-highlight

The code is using another font than the rest of the text but it’s still not yet syntax highlighted. Let’s fix this. The way to make it syntax highlighted is to add a theme. You can go to the prism.js home page and see what themes are available. I liked the tomorrow night theme. To use this, add this line at the top of your blog-post.js file:

require("prismjs/themes/prism-tomorrow.css")

And your code will be highlighted!

highlighted-code

Google analytics support

Everything for Google Analytics is already prepared for you. Open gatsby-config.js. Look for the following code and add your GA tracking id:

{
  resolve: `gatsby-plugin-google-analytics`,
  options: {
    //trackingId: `ADD YOUR TRACKING ID HERE`,
  },
},
GA is not enabled in dev mode. To try it out, make a production build and run it.

Production build

Now you’re ready to create a production build. Run the following command

gatsby build

It will create a build and put all files in public directory. To run your production build locally run the following:

gatsby serve

This will start a web server on port 9000 and serve your static files from there. Go to http://localhost:9000/ to check it out!

A note on caching

Gatsby uses caching heavily to make your site as quick as possible. The downside to this is that your app sometimes not reflect any changes you make. If you have a weird bug that seems impossible to do anything about try removing all cache. You’ll do it by removing the .cache file.

rm -rf .cache

Next steps

Now that you have an awesome blog, you want to share it with the rest of the world. To be able to do that you must deploy somewhere. There are a bunch of different options for this. I use Netlify and it’s awesome.


tags: