Moving to headless Ghost + Gatsby frontend

Some time ago, I was in a coworking space in Bali, sitting next to Aileen while she was working on cool things for Ghost. One of these things was Gatsby Starter Ghost. "A starter template to build lightning fast websites with Ghost & Gatsby".

At the time, I was not very busy at work, and hadn't done a side project in a while, so I wanted to try coding something new. I mentioned that to Aileen and she suggested I try the new Gatsby Ghost integration.

I liked the idea. A lot of my friends were excited about Gatsby and I wanted to try it myself. Also, I don't use React much - I normally work with Angular - and it's always good to learn different things โ˜บ๏ธ

I already had a Ghost site - the previous iteration of this website - so I decided to just rebuild that using Gatsby.

๐Ÿ‘ป How Gatsby + Ghost work

  • Ghost runs on a server (in my case, a Digital Ocean droplet). This is the API.
  • Gatsby is deployed to Netlify. This is the frontend.
  • Gatsby uses an API key and a URL to get content from Ghost.
  • Gatsby builds static HTML using that content.
  • Every time a new blog post is published or an update is made to the site, Gatsby rebuilds the static HTML (automatically or through manual deployment)

๐Ÿ’œ Advantages of Gatsby

Simple - site speed.

It's very fast. Ghost is fast by itself, but Gatsby takes that to another level.

Gatsby is a static site generator - which means there are no server calls for content. Everything is a static HTML page, already generated during a build process.

Plus it's so easy (and free!) to deploy it with Netlify - and also deploy stage environment branches, PR previews and more cool things.

๐Ÿ’” Disadvantages...

Yes, there are also some disadvantages.

Gatsby requires knowledge of React and GraphQL - so if you're not familiar with these, it will take some time and effort to get started.

Also, static HTML comes with its own limitations. For example, you can't use the window object (I found that out the hard way). And setting up Prism for syntax highlighting was a hassle. Many things just don't work out of the box.

๐Ÿค” Alternatives

If you don't want to use Ghost, you can also use Gatsby as a blog, on its own, with markdown files for posts.

And if you don't want to go through the trouble of setting up Gatsby - then Ghost is still pretty good on it's own! Gatsby just makes it faster and a bit more interesting from a developer's perspective.

But if you have some time to spend, and want a super fast website - and like playing with new technologies - then I say go for Gatsby Ghost!

โœจ The Process

To start, I followed the instructions in the Gatsby Starter Ghost repo - they are very clear and straightforward.

I didn't want to commit my Ghost API key, since my repo is public. So I set it as an environment variable on Netlify. I also added it locally with in an .env file at the root of my project. Here is what that looks like in gatsby-config.js:

require('dotenv').config({path: `./.env`});
const path = require(`path`)


let ghostConfig = {
    "development": {
      "apiUrl": "",
      "contentApiKey": process.env.GHOST_CONTENT_API_KEY
    "production": {
      "apiUrl": "",
      "contentApiKey": process.env.GHOST_CONTENT_API_KEY

I also set up the Ghost+Netlify integration, so that the static Gatsby site gets rebuild automatically whenever I publish a new post.

Now I had a working Gatsby site on a Netlify subdomain, pulling content from my existing Ghost. The Gatsby frontend was using a default theme. The next step was to add my own custom frontend.

๐Ÿ›  Rebuilding the frontend

I had already written a custom Ghost theme for my website (in vanilla JS and Handlebars). Now I had to rebuild that for Gatsby, in React.

This took a while but I learned a lot about Gatsby and React.

The final product looked identical to my previous website, but it was much faster. (My non-developer friends looked at me a bit weird when I showed them what I had been working on, and it looked exactly the same as before ๐Ÿคทโ€โ™€๏ธ)

Now that it was done I was ready to switch my domain to the new site!

๐Ÿ‘ฉโ€๐Ÿ’ป Hurdle 1: Domain

So here I ran into my first problem.

Until now, the frontend and the API were both Ghost - in one place - on a Digital Ocean droplet. Both on

But now frontend and API were going to split.

The frontend was moving away from Digital Ocean - to Netlify - so the domain had to be pointed there too.

What about the API? It stayed on Digital Ocean. So it was left without a domain. But the frontend still needed to call it somehow.

My first solution was to use the droplet's IP address instead of a domain for the API.

That worked fine for the text content (which was pulled and rendered), but not for the images (which were only linked).

Gatsby's static pages now linked to images directly on the droplet's IP. And since IPs don't have https, all images were now loaded over http, which is considered insecure - so browsers sometimes refused to load them.

So in short, some users were not seeing any images on my website any more. Which is a bummer, since my travel posts are photo-heavy. I even had random people messaging me to tell me about this (and I'm not getting huge traffic here so that was significant).

Confession... I left it like this for a whole year (or more) before coming back to fix it.

To fix, first I tried creating a subdomain - - and pointing that at the droplet with the Ghost API.

It didn't work, because the domain was on Netlify DNS - so the certificate was provided by Netlify - but I was trying to point it at Digital Ocean.

Then I ssh-ed into my droplet and messed around with certs and nginx for a while, but nothing worked ๐Ÿ™ƒ So I gave up and bought a new domain for my API.

I went on Porkbun and searched for the cheapest polinakocheva domain I could find - which turned out to be for $2.50/year. So I bought that, and set it up on my droplet with nginx and certbot. My Ghost API finally had a secure https URL โœจ

That solved the issue and all my images are loading now, without security issues!

๐ŸŒ™ Hurdle 2: Dark Mode ย 

Now everything was done and my Gatsby Ghost website was all good! So I decided to have some more fun and add a cool feature: dark mode!

I love dark mode toggles on apps and websites, so I really wanted to implement this in one of my own projects. I figured it would be mostly CSS and some logic to toggle it on/off... I was wrong.

First I designed the dark mode view and wrote the CSS for it, which was really fun.

Then I added a cute toggle component in the corner of my website.

My plan was simple. I was going to store the value of that toggle ('dark' vs 'light') in localstorage. Then I would show different CSS depending on that value. (If no value was stored it would just default to a light theme).

That worked fine... until I started clicking around in dark mode. It was flickering ๐Ÿ˜ฌ The light CSS would still show for a second before switching to dark, whenever a page was loaded.

This happened because React was already rendered on the server, in default (light) mode. Then it would render again in the user's browser, taking into account the dark preference - but with a slight delay, which produced the flicker.

So I looked for another solution and found this amazing blog post:

Looked like Josh had run into the exact same problem with his Gatsby website and found a great solution - which I implemented as well, following his instructions.

It's pretty complex and Josh does a much better job at explaining it than I would, so I'm not going to repeat it here - you can just read the blog post instead.

I should mention that this solution requires Styled Components, which I had never used before. And honestly, I'm not a huge fan of CSS in JS... So I only did the styles that were needed for the dark mode. I left everything else in regular CSS files. But it was still interesting to try a new thing.

I also had to figure out Theme Context, which took a while since I'm not very familiar with React.

In total, the whole Dark Mode thing took almost an entire weekend! But I'm very happy with the result and learned some cool things along the way โ˜บ๏ธ

And that concludes the work I did on this project. I did develop some other cool features (like the map on my homepage), but I should probably talk about them in separate blog posts, as this one is already well over 1000 words!

๐Ÿ‘€ TLDR;

The main benefit of Gatsby + Ghost is a super fast website, using an awesome CMS.

It takes a bit of time to set up - compared to the one-click install Ghost on Digital Ocean - and requires programming knowledge, especially React. But once done it's easy to deploy, develop, and customise as you'd like.

Personally I'm very satisfied with it and definitely recommend it to developers that want to publish a blog. โœจ