I’ve been running my "b:log" on WordPress since late [2006](/2006), but today I give you the node backed blog.
This is a two part blog post, the first covering why I moved, what I tried and a few of the high level issues I ran in to. Part two will cover some of the technical detail that goes in to running my blog on the new node platform.
These posts are not intended as walkthrough on how to do it yourself, but simply sharing my experience and bumps I ran into on the way, hoping to impart some useful knowledge along the way.
[](https://training.leftlogic.com/buy/terminal/cli2?coupon=BLOG\&utm_source=blog\&utm_medium=banner\&utm_campaign=remysharp-discount)
[READER DISCOUNTSave $50 on terminal.training](https://training.leftlogic.com/buy/terminal/cli2?coupon=BLOG\&utm_source=blog\&utm_medium=banner\&utm_campaign=remysharp-discount)
[I’ve published 38 videos for new developers, designers, UX, UI, product owners and anyone who needs to conquer the command line today.](https://training.leftlogic.com/buy/terminal/cli2?coupon=BLOG\&utm_source=blog\&utm_medium=banner\&utm_campaign=remysharp-discount)
[$49 - only from this link](https://training.leftlogic.com/buy/terminal/cli2?coupon=BLOG\&utm_source=blog\&utm_medium=banner\&utm_campaign=remysharp-discount)
Over the years I’ve had all the injections of Viagra adverts and the like over and over and over. Whenever I want to change anything, I’d tend to give up, and for a few years now, I’ve really wanted the source of my blog posts available in (something like) github.
This post is about the move and how I run my blog now.
My goals[](#my-goals)
In a totally ideal world, I wanted:
-
A fast blogging platform (not particularly for publishing, but for serving)
-
Backed by JavaScript (Node specifically) - because it’s the most familiar language to me
-
Edit links for posts to go to github allowing anyone to make a suggested edit
-
Archives and tag listings
-
URLs would be customisable (because I have old URLs that I want to support)
-
Could run on a free hosting platform like Heroku
-
As a bonus, I could hack and improve the system
TL;DR here’s the full source of my blog as it is today, on github: [https://github.com/remy/remysharp.com/](https://github.com/remy/remysharp.com).
Ghost[](#ghost)
I knew that I wanted to move to a node backed platform. Ghost seemed like the best fit, and I’ve had the pleasure of meeting and listening to John O’Nolan and Hannah Wolfe speak about Ghost, and I complete buy into the philosophy.
Exporting WordPress posts (and pages) to Ghost was actually very simple (I used the developer version of Ghost locally).
The only bump in the road was the error messaging during the Ghost import was pretty vague. But checking the devtools console yielded the answer, a 324 from my server during the upload process. So I tweaked nginx to allow for larger files to be uploaded and bosh. Fixed.
The next trick was the comments - which Disqus seemed like the default that everyone moves to. Obviously nothing to do with Ghost, but this process was tricky. The best advice I can give if you’re doing this and keep hitting failed imports is: validate the XML (w3c validator is just fine), and hand-fix the invalid XML.
Why I didn’t stick with Ghost[](#why-i-didnt-stick-with-ghost)
For the record, I think Ghost is an excellent platform for most users, particularly if they’re coming to blogging for the first time or wanting to shift away from WordPress.
However, being a developer I wanted to add a few custom tweaks, specifically I wanted an archive page, a handful of URL rewrite rules and a few of the Ghost ways of doing things weren’t quite what I wanted.
One particular example is all my old WordPress posts had split markers in them which Ghost doesn’t support. They do have support for creating excepts, but if you want HTML you can’t (at time of writing) append a read more to the link.
I tried to contribute to the Ghost project, but I ended up going down a rabbit hole for what was effectively a tiny change (submitting a pull request to a Ghost dependency Downsize).
The (understandable) problem is that Hannah and the Ghost team are producing code that works in a great deal of environments and so a quick PR here and there are great, but I can understand why they’re not merged in right away if at all: there’s a much bigger picture to consider.
I thought about just forking Ghost and permanently running my own version, but there’s a fairly big system to inherit when all I’m doing is serving pages…which I had done with Harp.js before.
So I made the jump to Harp.
Ghost to Harp[](#ghost-to-harp)
Harp is a static site generator. I’ve used it in the past for [our conference site](http://2014.full-frontal.org) this year and for the [JS Bin help & blog](https://jsbin.com/help) so I was already familiar with it.
However, harp requires static markdown files, so I went about connecting to the Ghost database via sqlite3 and exporting each of these records out as a static HTML file, whilst building up the _data.json
file required by harp to represent the metadata.
The code I used to convert is on github here: [remy/ghost-harp](https://github.com/remy/ghost-harp). Disclaimer: I wrote this for my own database and requirements, so this may not work for you out of the box.
The conversion process is pretty simple, read the sqlite database, write to files. So I ended up with a folder structure like this:
.
├── harp.json
└── public
├── _data.json
├── about.md
├── blog
│ ├── 2007-moments.md
│ ├── 8-questions-after-ie-pissed-the-community-off.md
│ ├── _data.json
│ ├── _drafts
│ │ ├── _data.json
│ │ ├── my-velveteen-rabbit.md
│ │ └── why-i-prefer-mobile-web-apps-to-native-apps.md
│ ├── a-better-twitter-search.md
│ ├── wordpress-tagging-and-textmate.md
│ └── youre-paying-to-speak.md
├── talks.md
└── twitter.md
Some contents are going to be in HTML, but Ghost seemed to put my HTML posts in the markdown column (and since it’s valid, it doesn’t really matter).
One significant tweak I made was to put the post title into the post itself. For example, if you look at the source [about](https://github.com/remy/remysharp.com/blob/main/public/about.md) page, you’ll see the title in the markdown. Ghost separates out the title and the body when you’re editing, but I wanted a single markdown file.
The next task was to fire up harp and have it running from my newly generated public
directory.
Harp[](#harp)
Now that all my content is in the public/blog
directory (via my little rewrite script) harp could serve my content. Using a simple (empty) harp.json
as the config, harp automatically knows to serve anything under public
as the root of the site (i.e. /blog/foo
will serve the file /public/blog/foo.md
):
My specific requirements for using harp were:
-
Serves static content (so I’d have to compile to static .html)
-
Serves in production without the
.html
extension visible -
Support rewriting of URLs, so that I could maintain my original URL structure of
/<year>/<month>/<day>/<post>
rather than pointing to/blog/<post>
-
I really wanted an archive, since I was simplifying a lot of my blog design, and losing a lot of navigation
In the end, I had to create my own custom server.js
that would run a bespoke router (I did use an existing library, but I needed changes, so I forked my own copy).
Harp certainly made things harder than using Ghost, but I had the flexability I needed.
I’m particularly proud of the [archive](/archive) page, partly because I managed to write it entirely with Jade (which over the years I’m slowly starting to warm to) and partly because I now have a page that lists all my posts since the first in 2006!
The version I’m running today satisfies all the goals I outlined at the start of the project, and more.
A few bonus features I built are:
-
I can add
/edit
to any page to quickly jump to github to edit (along with edit links being on all the posts) -
All the old demos and uploads from my WordPress site are hosted on Amazon S3 and redirected to via my
server.js
-
My development environment is slightly different to production, such as drafts are visible and the disqus comments are removed
The one thing I’d like harp to be better at would be knowing what to regenerate. Due to this my release process involves rebuilding the entire blog site (\~300 posts) and then pushing the changes to github and then heroku (where I’m now hosting my blog) - though this is effectively an rsync, so it’s not everything that goes up.
The final product[](#the-final-product)
The final product and platform consists of:
-
Statically generated content in
/www
-
Source control in github
-
Production is hosted on a single dyno on Heroku
-
Using [dnsimple](https://dnsimple.com/r/5bc02f2ef8976f) for
ALIAS
hosting to the heroku instance (so I can serve "naked" domains) -
CloudFlare fronts the production blog
-
The major and minor version are used to cachebust the CSS & JavaScript, due to this, it means changes to content are a patch release and all others are minor (or major) releases
-
The release process is a bash-like makefile that does all the compiling and revisioning for me
So my whole release process for this blog post is now:
$ make release-patch publish
And boom, just like that, you’re reading the post!
In part 2, I’ll explain some of the code that’s used to drive my blog and some of the tricks I had to use to get harp to play exactly the way I wanted.
Published 18-Sep 2014 under #web. [Edit this post](https://github.com/remy/remysharp.com/blob/main/public/blog/wordpress-ghost-harp-pt1.md)
Comments
Lock Thread
Login
Add Comment[M ↓ Markdown]()
[Upvotes]()[Newest]()[Oldest]()
Betina Jessen
0 points
3 months ago
One particular example is all my old WordPress posts had split markers in them which Ghost doesn’t support. They do have support for creating excepts, but if you want HTML you can’t (at time of writing) append a read more to the link. [Calendar June 2023](https://www.typecalendar.com/june-calendar.html)

David Kaneda
0 points
9 years ago
Hey Remy- Loving these posts. Just FYI (and sorry for the plug), but you might want to check out Buckets, a project I’m working on: Some similarities with Ghost, but tailored more to the general "content management" crowd, as well as to web designers specifically. In fact, I’m in the process of building in Harp support for frontend assets. Anyway, check it out if you get a chance! http://buckets.io

Andrew
0 points
9 years ago
I love the idea of using a static site generator for blogs, but personally, I haven’t found that to be a big enough reason to do it. I’ve been running self-hosted WordPress ever since I switched from Blogger in 2009, and honestly I have no complaints. Theming can be a bit confusing because of legacy stuff but I keep my designs simple anyways, and the dashboard is nice (but certainly not Medium-levels of minimalist bliss.) I dunno.
I’m glad you’ve found something your happy with, and I might join you some day. But I haven’t had a real reason to switch from WordPress in five years, and for personal blogs I doubt I ever will.

Mike Ward
0 points
9 years ago
I had a similar journey. In the end I wrote a super light blog engine that uses plain markdown files mostly because I wanted something in C# (the language I’m most comfortable with). It’s not a static site, but it runs as fast as one (http://mike-ward.net). I came to similar conclusions about files on Github, wanting an archive, legacy routing, etc. It runs very nicely on a free Azure Web site instance. Different technology, similar ideas. Thanks for sharing your story.

Ethan
0 points
9 years ago
Very interesting! Looking forward to the second part!\ I ran my website off of straight HTML for a while, then moved to jekyll, and then finally to using Harp as is right now. Always fun to hear about the evolution of someone else’s site!
[Commento](https://commento.io)
Search for… Search
-
[Home](/)
-
[Search](#search) *
-
[Previous](/2014/09/24/my-velveteen-rabbit "My Velveteen Rabbit")
-
[Next](/2014/10/06/what-is-a-web-app "What is a \"Web App\"?")
[WordPress → Ghost → Harp (part 2)](https://remysharp.com/2014/09/30/wordpress-ghost-harp-pt2 "Permanent Link: WordPress → Ghost → Harp (part 2)")
I wrote about moving away from WordPress to Ghost and then to Harp in [part 1](/2014/09/18/wordpress-ghost-harp-pt1), this post details some of the specifics of my blog’s implementation.
[](https://training.leftlogic.com/buy/terminal/cli2?coupon=BLOG\&utm_source=blog\&utm_medium=banner\&utm_campaign=remysharp-discount)
[READER DISCOUNTSave $50 on terminal.training](https://training.leftlogic.com/buy/terminal/cli2?coupon=BLOG\&utm_source=blog\&utm_medium=banner\&utm_campaign=remysharp-discount)
[I’ve published 38 videos for new developers, designers, UX, UI, product owners and anyone who needs to conquer the command line today.](https://training.leftlogic.com/buy/terminal/cli2?coupon=BLOG\&utm_source=blog\&utm_medium=banner\&utm_campaign=remysharp-discount)
[$49 - only from this link](https://training.leftlogic.com/buy/terminal/cli2?coupon=BLOG\&utm_source=blog\&utm_medium=banner\&utm_campaign=remysharp-discount)
Technical overview[](#technical-overview)
I’m using [Harp](http://harpjs.com) which is incredibly easy to get running with, but I’m also running Harp as a dependency inside my own custom node web server which allows me to add a few bells a whistles to my implementation.
-
[Custom URL rewriting](#custom-url-rewriting)
-
[Static caching](#static-caching)
-
[Use of special helpers inside Harp, such as moment.js](#use-of-special-helpers-inside-harp)
-
[List of recently modified posts](#list-of-recently-modified-posts)
-
[Archive & tag pages without the repetition of files](#archive—tag-pages-without-the-repetition-of-files)
-
[Makefile based release process](#makefile-based-release-process)
Custom URL rewriting[](#custom-url-rewriting)
Since I was porting an existing blog, I wanted to ensure that the URLs didn’t change. This meant supported my old /year/month/day/title
format. Which over the years I dislike, but when I moved to Harp, I decided to drop the date from the body of my posts and allow the URL to speak for that metadata.
I also wanted to host my old downloads and demos on Amazon S3, but the URLs from old posts would be relative to my blog, so I needed to rewrite these.
I forked [router@npm](https://www.npmjs.org/package/router) to create [router-stupid@npm](https://www.npmjs.org/package/router-stupid) - which is essentially the same, slightly cut down, but importantly: if you modify the req.url
in a route handler, that would affect the subsequent matched routes.
Redirecting is simple:
/* redirect to s3 hosted urls */
route.all('/demo/{filename}', function (req, res, next) {
res.writeHead(302, {
location: 'http://download.remysharp.com/' + req.params.filename,
});
res.end();
});
Supporting my date base URL format was trickier. The actual file lives in /blog/<title>
so when the URL hits my static server, it needs to be in that form. So supporting date base URL requires:
-
The URL format is correct
-
The title of the post actually finds a post
-
The date in the URL matches the date for the post
/* main url handler: /{year}/{month}/{day}/{post} */
route.all(
/^\/([0-9]{4})\/([0-9]{1,2})\/([0-9]{1,2})\/([a-z0-9\-].*?)(\/)?$/,
function (req, res, next) {
var params = req.params;
// the title slug of the url
var post = blogs[params[4]];
// make sure we have a real post before even proceeding
if (post && post.date) {
// test if the date matches
// post.date is a timestamp, so splitting gets us the date
var date = moment(post.date.split(' ')[0]);
var requestDate = params.slice(1, 4).join('-');
// compare the date of post _in the same format_ as requestDate
if (date.format('yyyy-MM-dd') !== requestDate) {
// if it's not good, move on - will likely result in a 404
return next();
}
// if there's a trailing slash, remove it and redirect
if (params[5] === '/') {
res.writeHead(302, { location: req.url.replace(/(.)\/$/, '$1') });
res.end();
return;
}
// this now allows Harp to pick up the correct post
req.url = '/blog/' + params[4];
}
// then let the rest of the router do it's work
next();
}
);
Static caching[](#static-caching)
Having used Harp in previous projects ([JS Bin’s documentation](https://github.com/jsbin/learn), [our event site](https://github.com/leftlogic/fullfrontalconf2014/) and [my business site](https://github.com/leftlogic/leftlogic)) and have created [harp-static@npm](https://npmjs.org/package/harp-static) which uses [st@npm](https://npmjs.org/package/st) to cache and serve static files.
So in my custom server, I point all routes down to the st
served content. I also support hitting the URLs without .html
at the end, again, to keep my old URLs working. I’d recommend checking out the [harp-static source](https://github.com/remy/harp-static) if this interests you.
Use of special helpers inside Harp[](#use-of-special-helpers-inside-harp)
At present, if you want to use a library inside Harp, like [moment.js](http://momentjs.com), the work around for this is to create a .jade
file with the source of moment.js (in this case) as script. Essentially the minified one line file prefixed with a -
character.
Then include the library in a common file, like the layout, and you have the helper available:
!- load the moment.js library for server side access
!= partial('/js/moment')
Except this would break during compilation to static files. I’m certain it’s to do with my custom serving process, but the path would somehow be wrong (so the library wouldn’t load and further down my code there would be exceptions in Jade about the library not existing).
The smart way around this is to expose a global from outside of Harp. So in my server.js
(that does all the routing, etc) I require
in moment.js and then I [expose it globally](https://github.com/remy/remysharp.com/blob/main/server.js#L26):
// this line, although dirty, ensures that Harp templates
// have access to moment - which given the whole partial
// import hack doesn't work consistently across dynamic vs
// compiled, this is the cleanest solution.
global.moment = moment;
Very simple, but now any Harp rendered file has access to moment.js. I use the same technique to expose the recently modified posts for listing on the homepage.
List of recently modified posts[](#list-of-recently-modified-posts)
The best way to get a list of all the post from outside of Harp (i.e. when you’re requiring Harp as a dependency), is to simply load the _data.json
file. It felt wrong initially, but it’s perfect:
var blogs = require('./public/blog/_data.json');
var slugs = Object.keys(blogs);
Now I have an object lookup by slug to the actual blog posts and I have an array of the slugs.
From this, I was able to fs.stat
all the blog posts and sort to return the 3 most recently modified and then using the previous trick, expose it globally so it’s included on my homepage (where recent
is the global exposed in server.js
):
each post in recent
li
a(href="#{ public.blog._data[post.slug].relative }") #{ public.blog._data[post.slug].title }
small updated #{ moment(post.date).fromNow() }
Archive & tag pages without the repetition of files[](#archive—tag-pages-without-the-repetition-of-files)
There’s two parts to this section. Firstly there’s the support for individual years or tags without duplication of (too much) code. Secondly is the Jade code that runs the archive listing.
Reducing duplication of code[](#reducing-duplication-of-code)
I could have a directory for each year there are blog posts (which I do have now) and each could contain the archive listing code. The problem (obviously) is duplication of code. You fix it one place, and (in my case, since I have 2006-2014) you have 8 files to update.
Instead, a single file index.jade
sits in tagged folder (and similarly with year folders) which contains:
!= partial('../../_partials/tag')
So we load a single partial. The tag.jade
file simply reads the path of the request, and uses the last part as a filter against all the posts:
tag = filter === undefined ? current.path.slice(-2, -1)[0] : filter;
posts = partial('posts', { filter: function (post) { return post.tags.indexOf(tag) !== -1 } })
.post
h1.title Tagged with "#{ tag }"
.post-content
ul
while posts.length
post = posts.shift()
if post.date
li
a(href="#{ post.relative }") #{ post.title }
small.date #{ moment(post.date).format('d-MMM yyyy')}
Note that partial('posts')
is a magic partial that simply returns an array of blog posts with the passed in filter applied.
Simple. Now if I want to add more support for tags, I just create a directory and the simple index.jade
and it works.
An archive listing[](#an-archive-listing)
A while loop that looks for a year change in the date, then works through each year, popping from the posts array looping through each post in the month.
It’s pretty cool (I think) because it works for entire years and all years: [archive.jade](https://github.com/remy/remysharp.com/blob/a198a4235634a3c7ac747ab403ac13bc49140a39/public/_partials/archive.jade)
Makefile based release process[](#makefile-based-release-process)
Disclaimer: this is a terrible use of a Makefile, it doesn’t leverage any of the benefits of make, and honestly, it could be a bash script. However, I like that I can run make publish
.
Taking a lead from [Makefile recipes for Node.js packages](https://andreypopp.com/posts/2013-05-16-makefile-recipes-for-node-js.html), my [makefile](https://github.com/remy/remysharp.com/blob/main/Makefile) allows me to run commands like:
$ make release-minor publish
The release-*
tasks will:
-
Bump the package version (according to patch/minor/major)
-
Compile Harp to static files
-
Commit all changes and tag
-
Push to github
The version bump has to happen first so that the version I used to cache bust in the compiled output is correct (otherwise you bump after the compilation, and then your released version is one step ahead of the version that appears in the source).
And that’s it! Here’s the full running [source to remysharp.com](https://github.com/remy/remysharp.com) - feel free to help yourself to anything that’s useful for your own blogs or sites.
Published 30-Sep 2014 under #web. [Edit this post](https://github.com/remy/remysharp.com/blob/main/public/blog/wordpress-ghost-harp-pt2.md)