Static Ghost - (with Gatsby & Netlify)

I've used ghost as my blogging platform for years now.  It's simple, requires minimal setup, and it's dead easy to theme.  It's never been the easiest to integrate with anything else, lacking even basic webhooks or API support, but recently this has begun to change.  The new Admin & Content APIs allow easy access to all the Ghost Content, which opens options for external UI's based on React, static sites and other integrations.

The possibility of converting my ghost blogs public hosting to a static site really appealed to me, so that's where I started.

But Why?

So why is having a static site better than just running off of Ghost? Well it might not be for everyone, but I could immediately see several benefits for me:

  • Resilience - A standard ghost install will require it's database and NodeJS app to run 100% of the time to serve content.  Scaling requires both these elements to be scalable, or aggressive caching.  With a static site, scalability is much, much easier.  In fact a lot of platforms will take care of it for you, such as an S3 bucket or Netlify.  
  • Security - There's not much more secure than a static site.  No NPMs to keep up to date, no NodeJS vulnerabilities.  Your choice of deployment/host may include keeping a web server up to date, but there's much less to worry about than with an application and database.  
  • Performance - This will vary by your choice of static site builder, and implementation, but whatever your choice, there's no NodeJS overhead OR database query time.
  • Portability - A static site can be hosted in a huge range of places - S3 buckets, Github pages, platforms like Netlify.  You don't need to worry about hosting containers, or NodeJS, versions or anything like that, if it can host the static site, you're good to go!
  • Affordability - Instead of needing ghost to run all the time, you can start it when you need to edit posts/site content. This means you wouldn't need the VM running ghost and it's DB only when it's needed, not 100% of the time.

The general theme, as you can probably tell is: There's much less to manage.

Building: Gatsby

Gatsby is the example provided by Ghost for usage of their Content API (, so I gave it a go first.

Gatsby is a tool for creating static sites using React.  It has a powerful plugin architecture, which amongst other things, allows you to plugin different data sources via GraphQL to generate the static site.

Ghost have provided one such plugin, and just about enough instructions to get up and running (to be fair to Ghost they make it clear that this use case is very new, and better documentation is coming).

To get going execute:

gatsby new gatsby-starter-ghost


This will give you a basic site with a default theme in react, which you can plugin to your ghost instance (either via environment variables or by .ghost.json file). To start developing just execute:

npm run dev

And you'll get a local dev server (running on localhost:8000), allowing you to iterate on your design and build up your desired functionality. Additionally gatsby include a graphql explorer (http://localhost:8000/___graphql) to help explore the dataset for your static site - which is a really nice feature.

I had some issues getting certain things to play nice together, even starting with an exact clone of the getting started git repo, but in general it was pretty painless, if you have even limited experience of React (like me), you'll likely be able make your blog look the way you want it to (in my case I went for a like for like replacement of the existing theme).


Hosting: Netlify

Netlify is a platform for hosting static sites, you give it a github repo, and it sets up the deployments for you, deploying when the git repo is updated, setting up CDNs, HTTPs (via lets encrypt), everything. It makes it dead easy to get up and running really quickly, and for everything I needed, it was free!

Once going through the process of signing up and adding my github repo, all I had to do was some minimal config to tell Netlify how my application was built (`gatsby build`) and we're good to go (ghost helpfully provided a starter project, with an included netlify.toml file). Getting up and running with a gatsby build that had worked locally on one of netlify's default subdomains took less than 10minutes including signup!

Getting domains transferred and HTTPS setup for those was a little trickier because Netlify won't provision the HTTPS certificate until the DNS is fully propagated, which led to a rather upsetting period where the site was offline, stuck between DNS propagation and no HTTPS certificate on Netlify.  But within 20minutes I was fully back up and running.

The final thing to do was to configure deployments when ghost content was updated.

(A co-worker initially told me about Netlify and when I first looked at it all I saw was the documentation about branching and similar, which put me off, but it turns out it's very possible to do trunk based development and triggers based on webhooks, which is exactly what I wanted)


As mentioned Netlify is built to trigger deployments when the git repo is updated.  This is great for when the theme is updated, or the how the data is gathered/mapped to graphql, but not so much for when ghost content gets updated.  Thankfully Netlify also supports deployments triggered from webhooks.

But we need to trigger deployments when Ghost content is changed.  Traditionally with Ghost this was REALLY hard.  I've seen people hijack slack webhooks and similar in the past to get a webhook when a post was published/updated/etc. Thankfully with the Content & Admin APIs came proper webhook support as well.  

First I configured Netlify to trigger deployments on a webhook, this providers you with a handy webhook url, which I then added to Ghost's integration section. To begin with I wasn't sure which hooks would fire when, and which ones I would need, but through usage I've determined the webhooks required are:

  • Post unpublished
  • Published post updated
  • Post published

This will cover any update to published content, anything else we don't care about as it won't make it to the static site.

Finally I took advantage of Netlify's build/deployment notifications to signal in slack when deployments were happening:

Did it work?

Yes! You're reading this on a static site, built on Gatsby, hosted on Netlify, a post that was written on my old ghost infrastructure. The deployments and webhooks were easy to set up, and I can make my blog look exactly how I want.  Building on top of it as it's a React app should also be dead easy. Performance is improved (see below), and it should be super resilient!

What wasn't so good? Well, again, Ghost make it really clear that using the content API and Gatsby together is really new, and the documentation is light, but I did suffer some frustrations where the getting started repo didn't work on a straight clone, with some issues around the siteMetadata graphql types, but I hacked remapped around that.  As well, the gatsby helpers aren't well developed yet (the tag helped outputs React warnings as well...), but I mention this more to reinforce that this is clearly new territory for Ghost, and not as a criticism, just be aware that you're doing something very new, and these things will happen.

The Netlify experience was super smooth in general.  As I say, I managed to go from signup to working site on an netlify subdomain in under 10minutes (once I'd got Gatsby working locally). Switching the DNS was a bit pain because of the time it took to be able to put the certificate in place, but I was lazy and didn't lower my DNS's TTL before making the switch, so it was 15minutes when I changed, so I probably could have made my life easier there, but even so, there was at least some guaranteed downtime, which for a production website could be quite problematic.

Should you do it? Maybe, this blog has always been a bit of a trialing ground for me, if it was a production site then obviously you're going to want things to be more polished before you dive in, but for a production blog, the benefits I've listed above could be potentially more key than they are for me.  That said there were some limitations I identified, that didn't effect me too much, but may cause you pain:

  • Preview, with my configuration, only published posts go live, no preview links for the static site, if you did get them up and running, it'd take a while to deploy and reflect the new preview anyway (see below).  You can preview on your ghost "backend" but that assumes you keep you ghost theme and static site theme in sync, which would be a bit of an overhead
  • Content update delay - with standalone ghost, content updates happen immediately, but with the static site it'll take 2-3 mins for the content changes to be reflected live.  This isn't a problem for me, not least given the potential benefits, but if you need instant updates to content, this maybe isn't for you.

And performance improvements?

Not bad, but no doubt there's room for improvement.

A massive improvement, as well as a slight boost in accessibility, all good news!

To PWA or not to PWA

By default the gatsby ghost starter includes the offline plugin.  This utilises a service worker to allow your site to be available offline, but I noticed a couple of issues (one minor, one major):

  • By default it will offer to install your blog to the user's homescreen on mobile.  I assumed no one wants to install my blog to their home page. Thankfully you can turn this off in the gatsby-config.js, in the gatsby-plugin-ghost-manifest section, set display to browser.
  • More concerning for a blog: The service-worker caching means that new posts and updates to posts seemed to appear indeterminately. For this reason I decided to disable the offline plugin for gatsby for now. I want to look into this more and determine what can be done to either control the caching, define the cache's ttl or similar.

I'd like to revisit the PWA implementation in the future, because it is very cool, including pre-fetching blog posts and more.  But I'd prefer to have up to date content for now!

Scheduled Posts

i8ramin asked on Twitter if the static site trigger would work with scheduled posts.  The good news is that it does! But, there is a gotcha I discovered, I hadn't updated the ghost instance to use the new ghost url (it has a different subdomain as the static site now lives on  Without doing that ghost's mechanism for  scheduling posts (it fires a request to itself) won't work.  So make sure you update your ghost url configuration value to be ghost, not the static site url.

What's Next?

Netlify helped me get up and running very quickly, but there's other options - S3 is a popular choice for hosting static sites, building our own nginx container to serve the content would allow it to be hosted in a wider range of places - in the interests of resiliency we could prepare the container and deploy it on demand.

So Gatsby was a success, but there's other options for static site generation out there.  So I'll likely have a look at those next.  I have some other ghost blogs I'll likely migrate to this system, and then look at what I can do to reduce costs of the ghost components. I'm going to see what I can do to extract some of the React Components I've used on my blog, and make them available, hopefully they will be of use to others.

Finally, the netlify pipeline I implemented was simple, but having a pipeline opens up new possibilities - also resizing & optimising of images, creating responsive images automatically, verifying content before publishing - far more than just, Build - Deploy.

Overall I'm really excited with the direction that Ghost is going, treating APIs and webhooks as first class citizens opens up a huge number of possibilities for ghost as a CMS platform, not just a blog. If you give ghost as a static site a go with Gatsby, or another tool, give me a shout on twitter, I'd love to hear how other people are doing it.

Header Image: Wikipedia