In 2015 I moved BirchTree to WordPress. I had danced with Squarespace and some static site generators before that, but WordPress was tried and true, worked with everything, and was hard to totally screw up. It was also very expandable, so I could add functionality however I saw fit.
I was happy, for the most part. But over the course of 2019 I started to fall out of love with it. The product itself was moving moving forward, and was slightly better with each release (although I never got used to the new Gutenberg editor), but the weight of WordPress, even imagined, got to me. I had tons of plugins installed to do everything from caching to payments to RSS/JSON Feed, and more. And these plugins had updates all the time that I saw in my admin toolbar and had to manage. Themes got updates as well…
And then there was the admin UI which it a little utilitarian for my style, but is also really slow at doing a lot of the stuff I do all the time. I would edit a post, hit save, and wait for a few seconds for the changes to actually take effect.
I could go on, and while there was nothing really killer, it was simply starting to feel like a giant train I was barely holding on the tracks. I don’t want this site to be very complicated, and while the end result was pretty simple, the scaffolding was always visible to me and it bothered me.
A New Path
The idea got some steam when the Statamic 3.0 beta started and I was drawn to my old love which I used in the 2013-14 era. Ultimately it still wasn’t for me anymore, not least of which was because it (and most other static site generators) didn’t handle large numbers of posts very well. This blog has 1,800 posts and it took upwards of 10-20 seconds for pages to load, so that was a no-go.
I tried Hugo as well, as it was pitched as being fast as hell. And to its credit, it was fast…very fast! But I struggled to get it running on a real server and even when I did it was hard to work with and just didn’t jive with how I wanted to work on my site.
I had all but given up on the idea of ever leaving WordPress, but then I came across Ghost, who I hadn’t looked at in years. I still remember their Kickstarter announcement in early 2013 and I toyed around with them in the beta period, but never did anything serious with them.
But I still remember their pitch as a WordPress alternative for bloggers. This also happened to line up pretty well with the launch of Ghost version 3.0 in late October so there was some excitement around the platform again. Ghost’s CEO, John O’Nolan, did an interview with Ben Thompson as well:
Ghost is at the intersection of all of the things that I love. We’ve managed to create a globally-distributed company which gives us the freedom to live lives we enjoy, the ability to work on a product that feels meaningful, in a market feels like it needs us and to me that’s enough. So my long term goal is for this to be a company and a product which is around for fifty, a hundred years.
Everything about this company gives me good vibes, and when I installed Ghost and started noodling around with it, those vibes only intensified. The tool was simple, but powerful. The UII was beautiful and fast. It had a nice writing interface, but it also worked perfectly with Ulysses, my writing app of choice. Lots could be configured in the UI, but I could ssh up to the server and change the source code myself.
And of course, it’s open source and lets me 100% own my data. I know many people don’t care about this, and the big monoliths will tell you that you actually can get your data out if you want, but that just doesn’t cut it for me. Ghost could go out of business tomorrow and this platform will still work for me forever.
How I Moved from WordPress to Ghost
I’m not going to lie, it wasn’t like:
Click export in WordPress
Click import in Ghost
If only! No, the process was more manual than I’d prefer, but it got me to a better place than I imagined. Here’s what I actually did.
I tried using the new Ghost export plugin which is supposed to export your posts and images, but it did not work on my site since I have 1,800 posts and 15,000 images; it just timed out as I believe the file it was trying to create was too large.
Instead, I exported my WordPress site into an XML file using the normal export mode in WordPress. It’s worth noting this brought over the words, tags, and metadata for all my posts, but no CSS, theme info, or images. More on this later.
Then I had to convert the WordPress XML file into a Ghost JSON file, which I did with the wp2ghost Node.js script. This involves a little terminal work, but not much and I had a JSON file in a couple seconds.
So now that I had my content, I needed to get Ghost running somewhere. I use DigitalOcean for all my hosting needs and have loved using them, so I just spun up a new droplet using their one-click Ghost installation. Pro tip: the Ghost installer here will ask you for your domain name and will set up an SSL certificate (through Let’s Encrypt) completely automatically during the setup, so make sure you change your DNS settings and let those propagate before running the setup.
When I tried to drag the JSON file into Ghost, it told me that the import file was formatted for Ghost 1.0, and the format had changed since then, so it would not work. I searched for a script to convert to the newer Ghost file format, but there does not seem to be one.
This is where things almost got off the rails…
The solution here was to install Ghost locally on my home Mac, specifically installing a 1.X version (which Ghost makes very easy with their command line tool). When this was running on my Mac, I was able to drag in my JSON file and get all 1,800 posts there successfully. Huzzah!
Next step was to upgrade that local version of Ghost to 3.0 and then use Ghost’s export tool to get a file in the format I needed. I import this into the version of Ghost running on my server and all my posts are finally there!
At this point there are tons of problems:
My URLs are not quite right (they are https://birchtree.me/post-title/
which means all of my internal links, link posts from others, and Google results are going to 404.
Every single image is hard coded to things like /wp-content/uploads/2019/12/image.png
which of course do not exist at all in Ghost and don’t load.
My theme is nowhere near correct.
None of my featured images carry over.
My analytics won’t run in this environment.
My fonts server from Hoefler & Co don’t work.
My ads via Carbon are busted.
RSS was no longer correct.
Here’s how I addressed each one of those:
This took a second to figure out, but was super easy once I did. Basically, I went to the content/settings/routes.yaml
file in my ghost folder and updated the URL structure to append/blog
in front of all blog posts.
This was solved by downloading the entire /wp-content
folder from my WordPress server and pasting it inside/assets
in Ghost. This immediately fixed all the hardcoded image links across my posts, which felt kinda magical. It was about 4GB across 15,000 files, so it took over an hour to transfer everything, but once it was there it all just kinda worked.
This was a big undertaking, but I simply found a pre-built theme for my site that was close-ish to what I wanted and then started poking around and updating things to look more like I wanted. Ghost uses something called “handlebars” for their theming engine, but most of the design work is just HTML and CSS, so I was able to pick that up very quickly. The biggest challenge was that I could not just paste in my old CSS since the whole page structure and class names were all different compared to WordPress.
Featured images are a problem I could not solve. Currently all featured images are blank and the only way to get them back is to cross-reference WordPress manually and re-upload them to the new site. This is going to take some time, so I have not done it yet, but will get at least my 2019 posts updated within the next few days.
Mint is no longer a viable option, so I had to switch to Google Analytics.
This was not too hard either, as I just needed to regenerate the font files on Hoefler’s website and upload them to my site.
This might have been easiest of all, as it was just a matter of moving Carbon’s embed code from one site to the other.
I use Feedpress to manage my RSS feed specifically for times like this where I make a change and my feed URL changes. I just had to get my RSS feed from Ghost and update my settings for the universal feed I hope everyone uses.
The Elephant in the Room
In the above section I presented the steps in a linear, orderly fashion, but that is not exactly how things went down in reality. That is how things went the third time I tried it, but my other 2 attempts were fraught with disaster. I made mistakes with not waiting for DNS to propagate, as well as going down wrong paths with URL routing that prevented me from accessing my own database, let alone serving any pages.
I also had tons of issues with getting my content in the correct way. It especially took me quite a while to figure out how to get the images to work correctly. In my second attempt I had everything working but I simply could not get SSL working and could not figure out why.
I got all of this (and other issue’s I’m not thinking of now) sorted out in just a few days, but there were some tense moments and some not-insignificant downtime for the site. Oh, and if you subscribe to the RSS feed, you probably got 10-15 posts reappear in your app that you had already seen: I’m sorry about that.
Was the move worth it? I think so! This is the first post I’m publishing post-migration and it’s going to take a little time for me to get fully acclimated to everything, but I’m amazed how “right” everything feels at the moment. My main interface is Ulysses, which makes my day-to-day experience basically the same, but I’ve really enjoyed the time I’ve spent in the Ghost admin pages and am really impressed with how fast I can do everything in there.
If you see anything terribly wrong with the site, please let me know on Twitter and I’ll get things sorted out as fast as I can.