Published on

Migrating to Gatsbyjs from Ghost 1.x.x

Authors

Why

I recently upgraded from Ghost 0.x.x to Ghost 1.x.x. But after using Ghost as a platform for some time I decided I wanted a change. Ghost is a very good platform and has exactly what you need in terms of easy setup, a selection of themes and a WSYWIG editor to write and manage your articles but I wanted even more control. I was also keen to try out the whole JAMStack approach. After looking into a number of options like Hugo, Jekyll and Gatsby I settled on Gatsby mainly because I am comfortable with JavaScript and because the platform uses interesting tech.

Gatsby -among other technologies- uses the following: ReactJS, GraphQL and Webpack. Gatsby has a plugin system and having a look at what is on offer they likely have a plugin for most of what I need and if they do not, I can simply roll out my own React component and use it. I also saw how fast Gatsby pages are due to it being statically generated and using server side rendered React components.

The last reason I switched to JAMStack is that I saw that Netlify offers free hosting of statically generated content even if you are using it with a custom domain name and HTTPS. This meant that I would not have to pay for hosting costs and the whole bit of managing my own server would not be a thing anymore. The main trade off I had to make was the loss of a few things like the WSYWIG editor, some niceties in posts like tags, delayed publishing of posts (for posts in progress) and a few other things. But I figured I could roll this out myself if need be or find an alternative solution which in and of itself would be a good learning experience.

Migration

Converting Ghost blog posts to Gatsby

The first step in the migration involves exporting your Ghost written blog content using the Ghost admin panel. This outputs a json file with all your content excluding images. The next thing is to convert this to the format that Gatsby uses. I chose to use the gatsby blog starter to expedite the process a bit which can be found here. The main difference between Ghost posts and Gatsby is that:

  1. Gatsby has individual markdown files for each post
  2. Images are referenced differently
  3. Markdown files have an header section (referred to as the front matter) which you can use to add meta data about your blog post

I thought this would be tricky but after doing a bit of research I saw that people had already written scripts for this:

const fs = require('fs')
const path = require('path')
const moment = require('moment')
const json = require('./greg-babiarss-blog.ghost.2017-05-13.json')

json.data.posts
  .filter((p) => p.status === 'published')
  .forEach(({ title, slug, published_at: published, markdown }) => {
    const date = moment(published).format('YYYY-MM-DD')
    const content = `---
title: ${title.replace(/\:\n/)}
date: "${date}"
path: "/${slug}/"
---
${markdown}
`
    fs.writeFileSync(path.join(__dirname, `/pages/${date}-${slug}.md`), content)
  })

The thing is this script is for Ghost 0.x.x which does not work against Ghost 1.x.x. So I forked this script and played around with it to get the final script I used:

const fs = require('fs')
const path = require('path')
const moment = require('moment')
const json = require('./8bitzen.ghost.2018-04-28.json')

const post_tags = json.db[0].data.posts_tags
const tags = json.db[0].data.tags

function ensureDirectoryExistence(filePath) {
  var dirname = path.dirname(filePath)
  if (fs.existsSync(dirname)) {
    return true
  }
  ensureDirectoryExistence(dirname)
  fs.mkdirSync(dirname)
}

json.db[0].data.posts
  .filter((p) => p.status === 'published')
  .forEach(({ id, title, slug, published_at: published, mobiledoc }) => {
    const date = moment(published).format('YYYY-MM-DD')
    const markdown = JSON.parse(mobiledoc).cards[0][1].markdown
    const associatedTags =
      '[' +
      post_tags
        .filter((t) => t.post_id === id)
        .map((t) => {
          const associatedTag = tags.find((tag) => tag.id === t.tag_id)
          return `"${associatedTag.name}"`
        })
        .reduce((accumulator, currentValue) => `${currentValue},${accumulator}`) +
      ']'

    const content = `---
title: "${title.replace(/\:\n/)}"
date: "${date}"
tags: ${associatedTags}
author: John Smith
draft: false
category: "Tech"
description: ""
template: "post"
---
${markdown}
            `
    const dir = path.join(__dirname, `/pages/${date}-${slug}`)
    if (!fs.existsSync(dir)) {
      fs.mkdirSync(dir)
    }

    fs.writeFileSync(path.join(__dirname, `/pages/${date}-${slug}/index.md`), content)
  })

To run this you:

  1. First update the script to use your exported Ghost json
  2. Put this json file in the same folder
  3. Run: node ghost-to-gatsby.js

This will then create a folder per blog-post where each folder has an index.md which contains the markdown content of your blog post.

Dealing with Posts with Images

You will need to download your Ghost content folder which contains the images that your posts use. Images using the page structure described in the previous section need to be referred to using the following syntax:

![your description](image-name.jpg)

Note how we refer to the image in the current directory (that the index.md is in) and not using a path like in Ghost blog posts. In my case I do not have too many posts with images so I used the following command to surface all posts with images (run this in the directory where your Gatsby migrated pages are):

grep -iERl '\.gif|\.png|\.jpg' .

I then moved the respective images to their associated post folder and updated the references to just have the image name and no path.

Connecting Gatsby to Netlify

You will need to place all your generated content into a new git repo which will be a repo against the Gatsby blog starter with all your pages in. You then login to Netlify using your Github/Gitlab account and Netlify should walk you through the rest of the process.

Once you have connected your repo to Netlify anytime you do a git push the changes are then automatically deployed by Netlify.

Updating DNS

Once you have confirmed that your changes are deploying successfully against the Netlify sub-domain that they provide your app you will need to go to your DNS provider and add a CNAME record to point to Netlify. On the Netlify side you will need to tell it what custom domain to expect so that it knows how to redirect it. Once you have done this you should be able to hit your deployed Netlify blog via your custom domain name.