How to use Ghost with Jekyll

Someone made an interesting query recently that I couldn’t help but take on as a challenge: Is there any way to use Ghost with Jekyll? Turns out there is, and for those that want to cut to the code here’s a handy gist.

GitHub - daviddarnes/netlify-plugin-ghost-markdown: Returns Ghost content as markdown files for static site generators like Jekyll to consume.
Returns Ghost content as markdown files for static site generators like Jekyll to consume. - GitHub - daviddarnes/netlify-plugin-ghost-markdown: Returns Ghost content as markdown files for static s...
Update: I ended up making an entire Netlify Build Plugin so almost any static site generator can use the Ghost API. Check it out!

I recently released a starter for using a Ghost site with the static site generator Eleventy, check it out the starter if you’re interested. I also wrote up about it on the Ghost blog, which includes links to the Ghost docs on how to use Eleventy with Ghost.

Set the scene

The relevancy of Jekyll to Eleventy? Well when we tweeted out the Eleventy post Mathias Aggerbo asked this:

Are there any way to use Ghost with Jekyll?

Mathias Aggerbo

For those of you who know me fairly well you’ll know that Jekyll is close to my heart, so I was keen to help find a solution.

But how? In my experience Jekyll isn’t known for working with APIs. Jekyll is designed as the more typical flat file CMS, taking text files (typically markdown files) and turning them into html files. Once more it’s Ruby based, so JavaScript API libraries aren’t going to be the smoothest things to plug in.

Enter stage right Phil Hawksworth:

A pattern I’ve used for a lot with a variety of SSGs is to have something like Gulp run the build. It pulls data from APIs, stashes it in the data files the SSG prefers, then generates the site with the SSG.

This can keep your options open so you can choose the tool you prefer.

Phil Hawksworth

gulp.js is a great tool, I often forget how useful and versatile it can be. The homepage text sums up gulp.js pretty well:

gulp is a toolkit for automating painful or time-consuming tasks in your development workflow, so you can stop messing around and build something

Gulp website

It’s designed to be used in the command line, as gulp tasks. For example, I could create a task called styles that turns a .scss file into a .css file, minifies that file and then clones it into my production directory. All by a running a single single command, gulp styles. Very handy if you want to make custom file processing workflows and already have some familiarity with Node / JavaScript.

Coding

Right, so we have our base components to achieve a link between Ghost and a Jekyll site:

  1. Ghost: Where our content will be sourced from
  2. Ghost Content API Library: How we’re going to get the content
  3. gulp.js: How we’re going to take that content and produce markdown files for Jekyll to consume

Because we’re using gulp.js all the following code is inside a single gulpfile.js. If you’re a bit unfamiliar with gulp I’d recommend checking out the gulp.js documentation on how to get started and it’s concepts.

Source content via API

The JavaScript Client Library for Ghost makes this a fairly clean process. I’m just using the demo API configuration so you’ll need to replace this with the credentials of the Ghost site, more info on how to get those credentials can be found here.

In a gulpfile.js:

const gulp = require("gulp");
const ghostContentAPI = require("@tryghost/content-api");

// Create API instance with Ghost credentials
const api = new ghostContentAPI({
	url: 'https://demo.ghost.io',
	key: '22444f78447824223cefc48062',
	version: "v4"
});

gulp.task('ghost', async function() {
	// Use API to get all posts
	// with their tag and author information
	const posts = await api.posts
		.browse({
			include: "tags,authors",
			limit: "all"
		})
		.catch(err => {
			console.error(err);
		});
	
    // ...
});

Construct an array of files

We’ve got our API data, but we want to loop through that data and produce files from each post item. Out of the box gulp is designed to take “file A” and turn it into “file B”. We’ll need to bring in some dependencies that will allow us to turn data into files.

Here’s what I ended up using:

const streamArray = require('stream-array');
const File = require('vinyl');

// gulp.task('ghost'...

  // Iterate over posts
  const files = posts.map(post => {

    // Getting some values from the post object
    const { published_at, slug, title } = post;

    // Take a single post and create a new file
    return new File({

      // Name the file based on the post date
      // and the slug
      path: `${published_at.slice(0,10)}-${slug}.md`,

      // Write the title of the post
      // inside the file
      contents: Buffer.from(title)
    });
  });

  // Stream the array of file instances into Node
  return streamArray(files)

    // Put the files in a '_posts' directory
    .pipe(gulp.dest('./_posts'));
});

I’ve done my best to explain it in the comments. Note that we’re using the published date and slug of each post to construct the filename and placing them in a /_posts directory, which follows Jekyll’s post filename format. The .slice(0,10) is to remove the time from the full date string.

Create frontmatter and markdown

The final part to this gulpfile.js task is taking the Ghost post data and formatting it in such a way that Jekyll will read it as a typical markdown post. Again, dependencies to the rescue!

Here we’re using template strings to construct the format of our markdown files, and then use handlebars to transform the variables into the values we want. We could just use regular JavaScript to put the data straight into the template, but this a bit easier to read and could be extended upon without overly complex template strings.

const Handlebars = require('handlebars');

// Create markdown template
// using handlebars templating
// (No indentation so it doesn't appear in the file)
const template = `
---
title: {{ title }}
excerpt: {{{ excerpt }}}
feature_image: {{ feature_image }}
tags:
{{#each tags}}
- {{ this.slug }}
{{/each}}
---
{{{ html }}}
`;

// Create a compiler function
// with the library and template string
const templateFunction = Handlebars.compile(template.trim());

// gulp.task('ghost'...

      // Pass the post to the template function and
      // in turn pass it to the content of the file
      contents: Buffer.from(templateFunction(post))
    });
  });

  // streamArray...

Another plus to using handlebars here is that it mirrors the Liquid templating language used in Jekyll, so anyone familiar with working on the project may have an easier time making edits and additions.

The template string I’ve used is more of an example, just exposing things like title, tags, html (the main content) etc. If you want to expose more of the Ghost API to your Jekyll site, like post attributes and other endpoints, you can check out the Ghost docs.

All together now!

Import your Ghost posts into a Jekyll project using Gulp
Import your Ghost posts into a Jekyll project using Gulp - gulpfile.js

If you click through to the gist you’ll see that I’ve added a package.json that you can easily copy too.

Pretty nifty method of bringing Ghost and Jekyll together I think. Feel free to chat with me on Twitter if you’ve got some improvements or are using this yourself!

PS. Thanks to Phil for giving me the inspiration, and thanks to egg for the refactoring and code review