On this page:

The Migration

After hosting for WordPress on Digital Ocean for years, I’ve decided to move to Jekyll on Github Pages. The decision came down a few key points:

  1. My content is infrequently updated and static - Wordpress was overkill
  2. It took more time to keep Wordpress and all the plugins updated than I spent on writing
  3. It’s $11/month I don’t need to pay
  4. Anyone who finds a mistake is welcome to submit a PR

The migration was pretty straight-forward once I understood what to do.

Export WordPress to XML

My first attempt was to use the Jekyll Exporter. It installed without issues but would not run successfully - every time I’d work through one issue there would be a another. I finally gave up on this one:

Fatal error: Uncaught Error: Class 'ZipArchive' not found in /var/www/roberthorvick.com/wp-content/plugins/jekyll-exporter/jekyll-exporter.php:420 
Stack trace: 
#0 /var/www/roberthorvick.com/wp-content/plugins/jekyll-exporter/jekyll-exporter.php(447): Jekyll_Export->zip_folder() 
#1 /var/www/roberthorvick.com/wp-content/plugins/jekyll-exporter/jekyll-exporter.php(337): Jekyll_Export->zip() 
#2 /var/www/roberthorvick.com/wp-content/plugins/jekyll-exporter/jekyll-exporter.php(102): Jekyll_Export->export() 
#3 /var/www/roberthorvick.com/wp-includes/class-wp-hook.php(307): Jekyll_Export->callback() 
#4 /var/www/roberthorvick.com/wp-includes/class-wp-hook.php(331): WP_Hook->apply_filters() 
#5 /var/www/roberthorvick.com/wp-includes/plugin.php(474): WP_Hook->do_action() 
#6 /var/www/roberthorvick.com/wp-admin/includes/class-wp-screen.php(421): do_action() 
#7 /var/www/roberthorvick.com/wp-admin/includes/screen.php(243): WP_Screen->set_current_screen() 
#8 /var/www/roberthorvick.com/wp-admin/admin.php(212): set_current_screen() 
#9 /var/w in /var/www/roberthorvick.com/wp-content/plugins/jekyll-exporter/jekyll-exporter.php on line 420
There has been a critical error on this website. Please check your site admin email inbox for instructions.

The fix was going to involve not just updating what PHP features were enabled, but actually re-compiling PHP to support zip. No thanks.

Instead I simply exported to XML and decided I would revisit the XML->Markdown problem later.

The steps for this are to

  1. Log into your WP Admin Dashboard
  2. Hover on Tools and select
  3. Choose “All content” (this is the default)
  4. Click “Download Export File”

Take note of the name - we’ll call it roberthorvick-export.xml for now.

Create github.io repo

  1. Log in to github
  2. Create a public repo named “bubbfat.github.io” - follow these instructions.

Install Ruby 2.7

I use Homebrew on MacOS so I ran:

brew install ruby@2.7

Then I modified my .zshrc file to include:

export PATH="/usr/local/lib/ruby/gems/2.7.0/bin:/Users/robert/.gem/ruby/2.7.0/bin:$PATH"

I then reloaded my shell and confirmed the correct version of ruby was being used.

Install Jekyll

With Ruby installed it was time to install Jekyll. I followed the instructions on the Jekyll homepage.

My commands were:

gem install bundler Jekyll
jekyll new bubbafat.github.io

I then tested by going into that directory and executing:

bundle install
bundle exec jekyll serve

The output (ignoring warnings) was:

done in 2.07 seconds.
Auto-regeneration: enabled for '/Users/robert/src/bubbafat.github.io'
Server address:
Server running... press ctrl-c to stop.

Then I opened a browser and headed to to check that things were good (they were).

Convert WordPress XML to Jekyll-compatible markdown

I followed the instructions on Kev Quirk’s blog. It suggests using wordpress-export-to-markdown.

My steps were:

  1. Clone https://github.com/lonekorean/wordpress-export-to-markdown
  2. Copy roberthorvick-export.xml from Downloads to the wordpress-export-to-markdown
  3. Rename roberthorvick-export.xml to export.xml
  4. Run node index.js --post-folders=false --prefix-date=true --wizard=false
  5. Copy the output to the Jekyll _posts directory.

The command to use was documented in the README.md - so big thanks to Will Boyd for not just creating the tool but also making it easy to use with Jekyll.

Pick a Jekyll theme

Jekyll ships with a default theme which was fine to make sure things were working but I wanted something a little better so I googled “jekyll themes” and ended up on Jekyll Themes.

I picked the theme I liked, paid, downloaded and followed the instructions in it’s README. It was basically “copy it into your repo and set some basic config”

So I did.

There was an issue there the index.markdown references a layout that does not exist (read your warnings when starting Jekyll). I don’t think I need index.markdown so I removed it and let index.html do the heavy lifting. Seems to be working fine.

Update DNS

My domain is registered through namecheap.com and used Cloud Flare (free) for caching. To reconfigure my domain I needed to

  1. Change the DNS on namecheap back to their BasicDNS setting
  2. Removed the domain from Cloud Flare
  3. Create a CNAME record as described here
  4. Create the 4 A Record entries defined here
  5. Wait about an hour for everything to update

Once this was done https://www.roberthorvick.com began properly serving the Jekyll content.

Generate and install certificate

Once the website was up and running at the correct domain, I wanted to install a cert so I could enforce https.

  1. Open the repo in github (while logged in)
  2. Click on “Settings”
  3. Click on “Pages” (URL is https://github.com///settings/pages)
  4. Click on Enforce HTTPS.
  5. Wait … wait … wait

Eventually it just started working.

Wiring up Google Analytics

I followed the instructions on Michael Lee’s blog.

Fixing the Look and Feel

Defaulting to the blog instead of projects

The template I choose defaulted the root URL to projects, not blog posts. I wanted the opposite. So I basically did:

mkdir projects
mv index.html projects
mv blog/index.html .

Then edited the _data\settings.yml to point the Blog menu to / (root) and the Projects menu to /projects/ - now it rendered the way I wanted.

Make changes and keep on pushing

With the theme applied I started making changes. Things like

  • Updating all the image references to the /images folder instead of _posts/images (the output from the exporter tool).
  • Updating the hero images
  • Updating the post preview images that show up in the index pages and as the hero image for the post
  • Created a category page with multi-level categories.

That last one was kind of hacky. I wanted to be able to create a template where I could have all the posts in a specific category be displayed but related posts should be together. I don’t know anything about how this should be done but what I did was create post categories that look like this:

  - "Programming#Erlang"

The idea is that this is the “Programming” category and the “Erlang” sub-category.

Then I used the idea for categorized post lists described here. But that displays every post for every category. I made some changes.

For example my page title is:

title: Programming

Then I modified the suggested page source (from the linked article) to be this:

<div id="archives">
{% for category in site.categories %}
  <div class="archive-group">
    {% capture category_name %}{{ category | first }}{% endcapture %}
    {% if category_name contains page.title %}
    {% assign subcategory = category_name | split: "#" | slice: 1 %}
    <div id="#{{ subcategory | slugize }}"></div>

    <h3 class="category-head">{{ subcategory }}</h2>
    <a name="{{ category_name | slugize }}"></a>
    {% for post in site.categories[category_name] %}
    <article class="archive-item">
      <h3><a href="{{ site.baseurl }}{{ post.url }}">{{post.title}}</a> ({{post.date | split: " " | slice: 0}})</h3>
    {% endfor %}
  {% endif %}

{% endfor %}


There are few changes that matter:

  1. I added a condition that makes sure the category contains the page.title (in this example “Programming”). Yes, it should be a startswith but I haven’t figured that out in Liquid yet. I gave this 5 minutes of effort.
  2. I assigned a new variable named subcategory which splits on “#” and picks the second word (“Programming#Erlang” -> “Erlang”)
  3. I display the subcategory but continue using the category_name in links and slugs
  4. I print the date the post was written with the title

Setting up Auto-Publishing Future Posts

I tried using the Jekyll drafts feature but I found it pretty annoying. The drafts are non-dated so publishing is more than just copying from _drafts to _posts - you also need to rename the file.

Then I looked at using the future post approach. Create a post like you normally do but have it dated in the future. The main problem with this approach is that when you publish to e GitHub there is nothing that causes your post to rebuild and render when the post time passes. But I figured there had to be a way so I start looking at Actions and discovered this post by Sean Killeen which clearly explained the steps to create an Action that uses cron scheduling to rebuild the site. I changed it from running hourly to running once daily at 4AM but otherwise it was a pretty easy configuration.

Photo by C Dustin on Unsplash