Upgrade to Jekyll 4 and Ruby 3
Introduction
Does this still work? Turns out it doesn’t! In this post, I go over my 4.5 year journey to fix that, driven by a new desire to more explicitly document my personal projects. Starting several years ago when I first drafted this post, I migrated to Jekyll 4 and Netlify, then just stopped. Now I have completed the journey and this site is built with Jekyll (4.4.1) and Ruby (3.4.2), all with automated dependency setup.
2020: Jekyll 3 -> 4
In September of 2020, I updated the site to run on Netlify. Which is great! I pay nothing, and pushes to master are deployed in a few minutes, with full support of Jekyll and custom plugins. This required upgrading to Jekyll 4 and fixing up dependency management to be reproducible on their build servers.
The first step was upgrading to Jekyll 4, dropping the rdiscount rendering
engine (no longer supported), and removing the Pygments syntax highlighter
(now built in). These updates bring GitHub flavored markdown code block
language syntax, so no more {{% highlight <foo> %}}
.
Netlify is awesome enough to build with whatever I have, but to do that I
had to actually learn Ruby package management. So I added a Gemfile
and
Gemfile.lock
to pin versions and used bundle exec
in my Rakefile. This
didn’t make my setup truly robust because Ruby was not pinned and nothing
else was automated, but that was a problem for future me.
My resulting Gemfile
:
source 'https://rubygems.org'
gem 'jekyll', '~>4.1'
gem 'jekyll-paginate', '~>1.1'
gem 'rake', '~>13.0.1'
2025: Ruby 2 -> 3
Now fast forward to 2025, and I actually want to get back to the blog, but it’s been two years since Ruby 2 has been updated. Since this is the only Ruby based system I use, I want everything automated and hermetic so it’s repeatable. My challenge is to get things working with Ruby 3.4.2, not install packages globally, and script setup. Build tasks are already done with a Rakefile (like a Makefile but using Ruby instead of shell).
I created the setup.sh
script and moved system dependencies and
rbenv
to install Ruby, totally dropping manually setup
instructions from the README. The best repository is one you can checkout,
doesn’t require docker, has run a single setup script and works on any
system. Then having the ruby version in .ruby-version
and rbenv
setup
in my environment ensures the right Ruby is used.
Ruby still doesn’t have something like Python’s virtual environments, but
you can configure bundler to install dependencies locally in the
repository. The vendor/gems
directory feels pretty standard, so I used
that and added it to .gitignore
. The resulting .bundle/config
is:
---
BUNDLE_PATH: "vendor/gems"
This gets me to a setup that builds, but Jekyll errors out because the version I have is not compatible with Ruby 3.4.2.
2025: Jekyll version update
Ruby is not Go, so Jekyll doesn’t just work with the newer Ruby. To close
this out, we have to update all our versions to the latest and then see if
everything works! I was frustrated by not finding an automated way of doing
this, so I manually updated the versions in the Gemfile
and then ran
bundle update
to install and freeze them. I also future proofed things by
explicitly using the logger package as needed for Ruby 3.5. The process for
this was:
- Search rubygems.org for each package used and update the
Gemfile
to the latest - Run
bundle update
- Discover the need for an explicit
logger
package - Find the latest logger version:
gem search --remote "^logger$"
- Manually add
gem 'logger', '~>1.7'
to the Gemfile
Now rake build
works and the site builds cleanly!
Bonus: Font Icon Fixes
As a small bonus, I discovered two things: font icons for my “socials” no longer showed up, and Google+ actually doesn’t exist anymore (since 2019 actually, and I sort of forgot it even existed). Fixing the latter was simply deleting a line of markup; the former required a bit more investigating.
My site is served and built as a branch--foo.netlify.app
URL while it’s
served from josephlisee.com
. Browsers are strict about CORS and
fonts (allowing a website served from host A to load content
from host B), but Netlify makes configuring this as easy as adding this to
my netlify.toml
:
[[headers]]
for = "font/*"
[headers.values]
access-control-allow-origin = "https://josephlisee.com"