ghost developer

Using Ghost as a headless CMS (part 2)

Finally I completed the task of hosting the Ghost CMS on Heroku (free) [that I ashamedly admit I started this task in January and then deferred], then using Netlify I build and host a Gatsby.js generated static site.

Shaun Wilde
a ghostly figure

So getting around to this took a lot longer than I thought it would but I eventually made the switch and now I am running my blog using a self hosted Ghost using Heroku and I am also using a Gatsby generated blog (uses React) with Netlify as the build and hosting tool. Why so long? Well there were a few integrations I wanted to make sure I could get working properly before I finally switched over from a managed Ghost platform.

For a starting point I used the JAMStack Ghost/Gatsby starter from styxlab and added a few tweaks to the theme that comes with it. I chose this theme as it has a few features built into it that I like e.g. Infinite Scroll, and looks to have an active community in case if I have any issues.

Disqus integration

I've been using Disqus for comment management for a few years and so I wanted to make sure I could hook that into the new theme. There is already a Disqus package for Gatsby and so getting started was relatively easy. What I initially stalled on was working out how to customise the Gatsby theme such that the Disqus component would load and to do this I needed to understand what a theme means in Gatsby; it isn't just CSS and images that I am more familiar with, with Gatsby you can also override the React components.

First I needed to identify the component I needed to shadow and then add the modified component to my own repository in the correct location. Looking at the template page.js in node_modules\gatsby-theme-try-ghost\src\templates there is a Comments component that looked like an ideal candidate and this component can be found in node_modules\gatsby-theme-try-ghost\src\components\common. The Comments.js is just a stub

/**
*
* Placeholder for Comments
*
*/

// The actual component
const Comments = () => (null)

export default Comments

To shadow this I copied this to my repository and placed it in its shadow location i.e. node_modules\gatsby-theme-try-ghost\src\components\common\comments.js to src\gatsby-theme-try-ghost\components\common\comments.js. Finally, I modified the component to bring in the Disqus component

import React from 'react'
import PropTypes from 'prop-types'

import { Disqus } from 'gatsby-plugin-disqus'

const Comments = ({id}) => {
let disqusConfig = {
    identifier: id,
}
return (
    <>
      <Disqus config={disqusConfig} />
    </>
)
}

Comments.propTypes = {
id: PropTypes.string,
}

export default Comments

To test this I updated my Disqus site to allow comments to appear on any site on my domain so I could view this on the development site I had hooked up on Netlify.

I did notice that not all the blog posts had hooked up properly but you can use the management tools within Disqus to move the comments onto the correct pages.

Algolia integration

I've been using Algolia as a search engine on the site for a few years and I didn't want to lose that functionality as I use it myself to find specific articles. Getting started was again easy as I followed the tutorial on the Gatsby site but because I was using Ghost rather than markdown then I needed to make a few tweaks. First I modified GraphQL in the starter algolia-queries.js to fetch the the from Ghost instead

const indexName = `sitePages`

const pageQuery = `
{
  pages: allGhostPost {
edges {
  node {
    id
    updated_at
    slug
    title
    plaintext
    excerpt
    tags {
      name
      description
    }
  }
}
  }
}`

function pageToAlgoliaRecord({ node: { id, ...rest } }) {
  return {
objectID: id,
...rest,
  }
}

const queries = [
  {
query: pageQuery,
transformer: ({ data }) => data.pages.edges.map(pageToAlgoliaRecord),
indexName,
settings: { attributesToSnippet: [`excerpt:20`] },
  },
]

module.exports = queries

Next, once the index was built on Algolia, I made some small changes to the default behaviour in Algolia to make the returned results a little more efficient (by default it returns the whole search document).

With all this in place and the Search component added to the site the results are presented like this

However after each update to the site or adding a new post (i.e. this one) the returned data being returned as part of the query results had reverted to returning everything again. It seemed that all my manual settings had been undone except for the excerpt:20 in the attributesToSnippet section which I was supplying as part of my query above, it seems I am required to supply all these settings rather than set them in the Algolia UI, so...

const queries = [
  {
query: pageQuery,
transformer: ({ data }) => data.pages.edges.map(pageToAlgoliaRecord),
indexName,
settings: {
    attributesToSnippet: [`excerpt:20`],
    searchableAttributes: [`title`, `tags.name, tags.description`, `unordered(plaintext)`],
    attributesToRetrieve: [`title`, `slug`, `excerpt`]
},
  },
]

With the above integrations eventually in place, and sticking, I flipped the switch and moved to being a completely self managed Heroku+Ghost+Netlify+Gatsby blog.

One final advantage of pushing directly to Aloglia during  the build process is that I can now deactivate the Zapier+Azure integration I was using when I first added Algolia support to my site.

[Update 18 Nov 2020] Some time back I blogged about creating a tags page so I could see all the tags I had created and find articles by those tags. I decided to repeat the task with the new site by taking inspiration from the starter theme and used a similar approach used to display the posts. Starting with a copy of index.js and customising the GraphQL query to fetch the tag data instead, including the post count. Then I created new components to finally display the tags in the same 1,3,2 pattern layout.

As always your thoughts are appreciated.