Migrating from Octopress to Jekyll
Back in 2014 I abandoned WordPress for Octopress. It’s been especially amazing for page load speeds, and I also enjoyed the fact that GitHub Pages are completely free - and I only need to pay for a domain name. Hosting a website can get expensive.
Octopress was a shortlived framework built on top of Jekyll, focused on blogging and designed to run on top of GitHub Pages. Unfortunately the development stopped in 2015, and now, 10 years later, I couldn’t set it up on a new machine due to most dependencies getting dangerously out of date.
I chose to migrate to vanilla Jekyll, since it’s a static site generator which is built on top of simple markdown and HTML files. Jekyll’s been around for some time, and I’m hoping Microsoft won’t be shutting down GitHub pages any time soon.
The whole process only took a couple of hours, and I’d like to document some highlights and lowlights. You might find it useful if you’re setting up a new Jekyll blog, or, like me, still have an Octopress blog that needs migrating.
Fresh setup
I went with a fresh Jekyll setup, by installing Jekyll and running jekyll new blog
. I successfully copied over old _posts
and images
, and ported the relevant parts of _config.yml
from Octopress to vanilla Jekyll.
Octopress uses liquid {% img %}
tags, which aren’t natively supported in Jekyll. I took the opportunity to convert those to markdown style syntax. I only have a few hundred posts, and I used a Vim macro to convert all {% img /foo/bar.png baz %}
to 
.
By default Jekyll comes installed with the minima
theme, which I found to be mostly sufficient for my needs. I was able to override specific theme files by copying them from gem installation location to my blog directory and modifying them. Turned out to be straightforward and customizable. For example, I transferred the way Octopress pagination looks by modifying _layouts/home.html
.
For backward compatbility, I also had to move RSS feed to /atom.xml
by modifying _config.yml
:
feed:
path: /atom.xml
I could immediately run the site locally with bundle exec jekyll serve --baseurl=""
.
Missing functionality
Two major things were missing straight out of the box: archive and category pages.
I grew attached to my archive page, and recreating it only took a couple of minutes. All I had to do is add an archive.markdown
page to the site’s root directory:
---
layout: page
title: Archive
navbar: Archive
permalink: /blog/archive/
---
{%- assign date_format = site.minima.date_format | default: "%b %-d, %Y" -%}
<div>
<ul>
{% for post in site.posts %}
{% capture this_year %}{{ post.date | date: "%Y" }}{% endcapture %}
{% unless year == this_year %}
{% assign year = this_year %}
<h2 style="margin-top: 1em;">{{ year }}</h2>
{% endunless %}
<li>
<a href="{{ root_url }}{{ post.url }}" itemprop="url">{{ post.title }}</a>
<span class="text-muted">| 📅 {{ post.date | date: date_format }}</span>
</li>
{% endfor %}
</ul>
</div>
Building category support turned out to be messier and more complicated. I didn’t want to write up a custom solution, and ended up with some technical debt I’ll probably have to address in the future (wink-wink, this will never happen).
I used jekyll-category-pages
gem, which worked okay-ish. The instructions on field-theory/jekyll-category-pages are extensive and aren’t too difficult to follow - I appreciated not having to write my own category pages, but I had to:
- Stop category pages from being automatically added to the navigation bar.
- Disable pagination on category pages, because for some reason it really didn’t work with
jekyll-category-pages
.
I also added my own basic category index pages by creating categories.markdown
:
---
layout: page
title: Categories
navbar: Categories
permalink: /blog/categories/
---
{% assign category_names = "" | split: "" %}
{% for category in site.categories %}
{% assign category_names = category_names | push: category[0] %}
{% endfor %}
{% assign category_names = category_names | sort %}
<div>
<ul>
{% for category in category_names %}
<li>
<a href="{{ root_url }}/{{ site.category_path }}/{{ category | slugify }}">{{ category }}</a>
</li>
{% endfor %}
</ul>
</div>
GitHub Pages
While GitHub Pages documentation is extensive, getting Jekyll to work with GitHub Pages took longer than I’d like to admit. Specifically, Gemfile
generated by running jekyll new blog
misleadingly tells you to comment away the latest version of the jekyll
gem and instead use the github-pages
gem:
# Happy Jekylling!
gem "jekyll", "~> 4.4.1"
# If you want to use GitHub Pages, remove the "gem "jekyll"" above and
# uncomment the line below. To upgrade, run `bundle update github-pages`.
# gem "github-pages", group: :jekyll_plugins
You don’t want to do that, oh no. Because the default GitHub Pages gem is stuck in the past on the 3rd version of Jekyll (and at the time of writing we’re on version 4), which caused all kind of hidden problems - including the fact that my URL slugs silently weren’t getting generated right. I switched back on the jekyll
gem and set up a custom GitHub action to deploy the site:
name: Deploy Jekyll site to Pages
on:
push:
branches: ["master"]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
# Build job
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Ruby
# https://github.com/ruby/setup-ruby/releases/tag/v1.207.0
uses: ruby/setup-ruby@4a9ddd6f338a97768b8006bf671dfbad383215f4
with:
ruby-version: '3.1' # Not needed with a .ruby-version file
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
cache-version: 0 # Increment this number if you need to re-download cached gems
- name: Setup Pages
id: pages
uses: actions/configure-pages@v5
- name: Build with Jekyll
run: bundle exec jekyll build --baseurl "$"
env:
JEKYLL_ENV: production
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
# Deployment job
deploy:
environment:
name: github-pages
url: $
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
Don’t forget to set “Build and deployment source” to “GitHub pages” in the repository settings to actually use the action.
My Octopress blog was set up in a source
Git branch, and content was generated into the master
branch. I wanted to change that to have the source in the master branch (the action above won’t work without that), and I was able to replace my master
with source
with the following set of commands:
git checkout master
git pull
git checkout source
git merge -s ours master --allow-unrelated-histories
git checkout master
git merge source
We merge the master
branch into source
using ours
merge strategy (effectively ignoring the master
branch history), and then merge that back into master
.
Positive experience
All in all migrating to Jekyll has been a great experience, which is a testament to Jekyll community’s dedication to thorough documentation. Knowing that Jekyll is a mature, maintained, and documented project, and that GitHub Pages infrastructure is reliable and supported, provides a sense of stability. I hope this results in Jekyll and GitHub Pages becoming a (reasonably) future-proof platform for my blog. But let’s check back in in 10 years - see you in 2035?