This year I migrated my blog from the famous WordPress blogging platform to Jekyll static site generator. The main reasons were the handling of code snippets, simplicity, and security. I think that WordPress is fine, but my own time with WordPress is certainly up.
Jekyll 101
Jekyll is a blog aware static site generator. It’s one of the oldest and most popular static site generators out there. The three pillars of Jekyll are:
-
It’s simple
No database, no comments, no updates. After a little setup, Jekyll frees your mind and let you focus on your content.
-
It’s static
Bring your Markdown, HTML and CSS. The result is a static site. No backends.
-
It’s blog-aware
Jekyll features all the bits to make a successful blog such as posts, pages, categories, custom layouts and many other things via its plugins.
Jekyll is not a content management system (CMS) so the comparison with WordPress might be unfair (both ways), however, both are widely used these days for blogging and Jekyll got a +1 this year from me.
Installation
Jekyll is written in Ruby. This is great for the ability to extend its core functionality with plugins but bad for the requirement of having Ruby installed on your system.
So first, we need to install Ruby. On Fedora you can install system Ruby with:
$ dnf install ruby -y
Then we can install Jekyll:
$ gem install bundler jekyll
Bundler, a Ruby gems manager, is optional but can help you manage all the site dependencies in the Gemfile.
Structure
Once installed, it’s easy to tell Jekyll to generate a new site to have a peek inside:
$ jekyll new my-awesome-site
$ tree my-awesome-site
my-awesome-site
├── 404.html
├── about.markdown
├── _config.yml
├── Gemfile
├── Gemfile.lock
├── index.markdown
└── _posts
└── 2020-10-16-welcome-to-jekyll.markdown
Each project has a few key files and directories, Here are the main ones:
Gemfile
andGemfile.lock
are standard Bundler files to manage Ruby dependencies._config.yml
contains the main Jekyll configuration._posts
directory is a jar for published blog posts.index.markdown
as your site homepage.about.markdown
as your site example standalone page.404.html
as your site fallback for HTTP 404 Not Found error.
An important thing to keep in mind is that Jekyll requires post files to be named in the following format:
YEAR-MONTH-DAY-title.MARKUP
There are also other directories in a typical Jekyll site that might make their way in. Here are some that you might come across:
css
directory as a jar for site stylesheets.img
directory as a jar for site images._drafts
directory as a jar for unpublished drafts._uploads
directory as a jar for posts images (I like to split design images to post images)._layouts
directory containing your page, post, and tag layouts._includes
directory containing repeatable elements such as header and footer.
Configuration
The _config.yml
YAML file is the source of configuration.
Here are some of its parts.
Basic name, description, author, layout:
name: Notes to self
title: Notes to self
tagline: Software engineering, ...and bootstrapping
description: Josef Strzibny's publication on software engineering...
author: strzibny
permalink: /:title/
layout: post
Markdown used:
markdown: kramdown
kramdown:
input: GFM
syntax_highlighter: rouge
Sass setup:
sass:
sass_dir: _sass
style: compressed
Default value:
defaults:
- scope:
path: ""
values:
image: /img/nts.png
Plugin definitions:
plugins:
- jekyll-sitemap
- jekyll-paginate
- jekyll-seo-tag
- jekyll-archives
And some of the settings for plugins:
jekyll-archives:
enabled:
- tags
- month
layouts:
month: month
tag: tag
permalinks:
month: '/:year/:month/'
tag: '/tag/:name/'
title_prefix: Archives
paginate: 5
paginate_path: "/page/:num"
I mention some of these settings below. Feel free to start with the default and incrementally add new settings with each added feature.
Templates
Jekyll uses the Liquid templating language from Shopify to process site templates.
Content is injected by using variables surrounded by two curly braces as showed on the following snippet of this blog’s _layouts/default.html
layour:
<nav>
<a title="Notes to self by Josef Strzibny" href="/"><strong>/home/nts</strong></a>
</nav>
{{ content }}
<footer>
<a href="/about/">About</a>
</footer>
content
above is a variable that is provided for us by Jekyll.
Filters can modify the provided values:
{{ "Hello world!" | number_of_words }}
Tags provide control flow and helpers. They are started with {%
and ended with %}
:
{% link /assets/files/doc.pdf %}
I recommend going through the generated files to get the feel for Liquid and to see official docs on tags and filters.
Front Matter
A regular Liquid template in Jekyll will start with a so-called Front Matter, a special meta YAML block.
Here is a start of one of the post on this blog:
---
title: Getting Vagrant with libvirt support on your Fedora 20
layout: post
permalink: /getting-vagrant-with-libvirt-support-on-your-fedora-20/
tags:
- fedora
- vagrant
---
I was ...
It defines the post title
, layout
, permalink
and tags
.
This block must come first in the template and be separate with three triple-dashed lines as you can see above.
Build and production
We can build our new site with jekyll build
or use jekyll serve
which will build the site and serve it immediately for preview:
$ bundle exec jekyll serve --drafts
By default, Jekyll will use http://127.0.0.1:4000
, so just open it locally with any browser. If we need to preview our drafts as well, we should add --drafts
option.
Once the site is ready for publishing we should build it with JEKYLL_ENV
set to production
:
$ JEKYLL_ENV=production jekyll build
This environment is useful for building up features we want to only see in production such as a snippet for tracking visitors. In any template we can use the following if
statement to separate the production environment:
{% if jekyll.environment == "production" %}
...only in production...
{% endif %}
Publishing
The writing routine is certainly something I had to change. Before I would log in to my WordPress instance and write my posts entirely from the WordPress admin interface. I could schedule the publishing, save drafts, or see the number of clicks per posts.
Since the build and management are more local, I recommend leveraging your preferred Markdown editor. I moved entirely to Sublime Text 3 where I already program and write. Here is a post on jekyll-sublime plugin that helps me with this.
Some other people might still want to use some kind of admin. There is Jekyll Admin, Netlify CMS or Prose.io that are worth checking out.
If you will host your site git repository on GitHub, you can also leverage their online editor, which is handy for quick fixes. I also started to leverage GitHub issues for my post ideas and notes.
Hosting
I am done with WordPress hosting. While I can imagine a project that I would start on WordPress, I don’t want to handle the hosting part of it. This is because I don’t want to deal with PHP hosting anymore. Some hosting providers have a one -click WordPress install, but then leave you on an old version of PHP and some plugins won’t install, etc. Managed WordPress would be my choice today, but it’s quite expensive.
Hosting is one of the best reasons to migrate off WordPress. By building the blog with Jekyll I don’t have to manage the PHP backend nor do I have to care about running a MySQL database. It’s also a reason I specifically evaluated only static site generators. It’s liberating.
And the cherry on top? There are many completely free static site hosting providers and many of them even integrate directly with Jekyll. They usually handle TLS certificates and offer a CDN as well.
GitHub Pages and Netflify integrate with Jekyll nicely. Render is another option for completely free static site hosting with CDN.
Assets
One thing to consider for blogs with a lot of images and media would be separate hosting for your assets. Doing a lot of images inside the main git repository does not scale well. Object storage like S3 or Digital Ocean spaces could be an option. I am still thinking of a good setup for the future.
Error pages
Error pages redirects depend on your hosting.
If you decide to use Netlify like me, see their redirects guide.
WordPress migration
Almost sold on Jekyll? Curious about how much work it takes to migrate a WP site?
There is an official importer that supports WordPress. The importer only converts posts with YAML front-matter. It does not import any layouts, styling, or external files (images, CSS, etc.). If your site is assets heavy, I would not recommend it.
There are however WordPress plugins that can get you started with the migration. One of them is WordPress to Jekyll Exporter.
It should create drafts and posts directories. Assets will be referenced from the wp-content
directory that you either leave as it is or optionally migrate the files out of it.
It does not migrate your design or comments though. Personally, I took this as an opportunity to create a new design for my blog from scratch and simply remove comments. I direct people to drop me a note on Twitter instead.
Building blocks
Let’s see how to implement and work with the main bits and pieces on a typical Jekyll programmer blog.
This section covers:
- Excerpts
- Tags
- Code snippets
- SEO
- Social images
- Sitemap
- Post updates
- Permalinks
- Favicon
- RSS and Atom feeds
- Search
Excerpts
Each post can define its own excerpt separator with excerpt_separator
in the Front Matter:
excerpt_separator: <!--more-->
This can be useful when migrating from systems such as WordPress.
Other than that, excerpt will be the first paragraph. If you are building a post listing you can edit the excerpt using Liquid filters.
For example, you can strip HTML and truncate to a designated length with the following:
{{ post.excerpt | strip_html | truncatewords:75 }}
Tags and archives
I recommend jekyll-archives plugin that can handle both archive and tag pages.
Add to Gemfile
gem "jekyll-archives"
and _config.yml
:
plugins:
- jekyll-archives
And you can start with the similar settings I use on this blog (also in _config.yml
):
jekyll-archives:
enabled:
- tags
- month
layouts:
month: month
tag: tag
permalinks:
month: '/:year/:month/'
tag: '/tag/:name/'
title_prefix: Archives
What these little settings do?
- It enables tags and monthly archives.
- It tells Jekyll to use
layouts/tag.html
for tag pages andlayouts/month.html
for archive pages. - It sets permalinks and a title prefix.
If you are curious, here is the simple tag layout I use:
---
layout: default
---
<h1><span>{{ page.title }}{{ page.date | date: "%B %Y" }}</span></h1>
<section class="main">
<ul>
{% for post in page.posts %}
<li><a href="{{ post.url }}">{{ post.title }}</a></li>
{% endfor %}
</ul>
</section>
And archive template:
---
layout: default
---
<h1><span>{{ page.date | date: "%B %Y" }}</span></h1>
<section class="main">
<ul>
{% for post in page.posts %}
<li><a href="{{ post.url }}">{{ post.title }}</a></li>
{% endfor %}
</ul>
</section>
Code snippets
One of the main reasons for leaving WordPress was the handling of code snippets. I never ever made them to work to my satisfaction on WordPress.
Luckily this is actually very straightforward on Jekyll.
Rouge is a pure-ruby syntax highlighter that Jekyll will happily use with a little configuration in _config.yml
:
markdown: kramdown
kramdown:
input: GFM
syntax_highlighter: rouge
Make sure the syntax_highlighter
is set to rouge
.
Then you need to pick a color theme. You can start with github
or monokai
.
Generate the styles you want with rougify
:
$ rougify style monokai > /path/to/css/file.css
And include the generated styles in your <head>
as:
<link href="/path/to/css/file.css" rel="stylesheet">
Code snippets will be automatically highlighted if the programming language used is specified:
```python
def function():
print('Yes')
...
SEO
There are few basic things to set up for a decent search engine optimization. Most of it can be handled by jekyll-seo-tag. It takes care of the following (taken from the project README.md):
- Page title, with site title or description appended
- Page description
- Canonical URL
- Next and previous URLs on paginated pages
- JSON-LD Site and post metadata for richer indexing
- Open Graph title, description, site title, and URL (for Facebook, LinkedIn, etc.)
- Twitter Summary Card metadata
Add as any other plugin in Gemfile
:
gem "jekyll-seo-tag", "~> 2.6"
And to _config.yml
:
plugins:
- jekyll-seo-tag
Then add the following just before </head>
:
{% seo %}
Finally, adjust as you like. On this blog, I am using the following in the config file:
social:
name: Josef Strzibny
links:
- https://twitter.com/strzibnyj
- https://www.linkedin.com/in/strzibny
- https://github.com/strzibny
- https://gitlab.com/strzibny
twitter:
username: strzibnyj
card: summary
I also recommend to do a Google site verification if you didn’t do it previously.
Social images
With jekyll-seo-tag
plugin mentioned above we can easily specify social images per post or page in Front Matter with:
image: /path/to/post/image.img
And adjust its properties by providing the whole object if needed:
image:
path: /img/twitter.png
height: 100
width: 100
You can also set a default image as a placeholder with the following snippet in the _config.yml
file:
defaults:
- scope:
path: ""
values:
image: /assets/images/default-card.png
If you want to automate the whole generation of social media images, you can look into the open-source Open Graph Image as a Service.
Sitemap
There is a jekyll-sitemap plugin to handle the sitemap generation for you.
Add to Gemfile:
gem "jekyll-sitemap"
And to _config.yaml
:
url: "https://hostname.com"
plugins:
- jekyll-sitemap
Read the plugin documentation if you need to exclude some pages.
Post updates
Post updates can be managed by updating last_modified_at
directive in Front Matter:
date: 2016-02-13T20:47:31+08:00
last_modified_at: 2016-02-14T00:00:00+08:00
---
Then in the template you can include the date within the HTML <time>
tag:
<p class="post-meta">
<time datetime="2020-10-16T00:00:00+00:00" itemprop="datePublished">Oct 16, 2020</time>
...
</p>
Permalinks
You can define custom permalinks with permalink
directive in Front Matter:
permalink: /about/
Here is how to use many of the variables available such as ::categories
, :year
, :month
, and :day
.
If you need to tweak the permalinks even further, I have previously written a post Changing URLs in Jekyll.
Favicon
If you need a new favicon, you can generate one with Favic-o-Matic.
RSS and Atom feeds
With jekyll-feed plugin you can have an Atom feed available in no time.
Add to Gemfile:
gem "jekyll-feed", "~> 0.12"
And to the _config
file:
plugins:
- jekyll-feed
The plugin will generate the feed at /feed.xml
. The path is configurable.
Then you can include the link to the feed as:
<link rel="alternate" type="application/rss+xml" title="RSS feed" href="http://your-site.com/feed.xml" />
Search
One thing that is not possible with a typical Jekyll site is to have a database-powered search. Your option is to implement a separate backend service (that would need hosting management), go with a search engine like Google (you could redirect to search with site:
scope), or do pure pre-generated front-end search.
For Fedora Developer we successfully used jekyll-lunr-js-search plugin. Unfortunately it’s unmaintained by now. You can look into Simple-Jekyll-Search as an alternative, but I don’t have a personal experience with it.
In my case, I decided not to implement a search function for now. I am not sure it’s needed for a typical blog. Nobody will miss it.
Analytics
With WordPress, I used a server-side analytics plugin which was very easy to setup. It gave me a simple overview of how I am doing. While I got a free hosting for my static blog with Netflify, I would have to pay $10 every month with the most basic plan to have access to analytics.
So I started with exploring my options which I summarized in Privacy-oriented alternatives to Google Analytics for 2020. The article mentions a lot of client-side services and open source tools from which I tried Fathom and Plausible in the last couple of months.
I am staying with Plausible for now, but I also recommend you to read my experience with Netlify analytics which I gave two months to compare. Choosing between client-side and server-side analytics is not easy, you might as well run both.
Testing
Most people won’t test their static sites the same way they won’t test their WordPress installation. And mostly you don’t need to. But I would actually recommend doing some simple testing.
Personally, I see value in checking that relative links work. You could then extend it to test that your references work as well (to delete dead links).
Here is my approach to testing static sites.
Is Jekyll worth it?
I spent a few days on the migration, but I am so relieved and happy with it! It also made me create a new minimal theme which I am improving as time goes. It’s light, fast, and easy to manage. I am not missing WordPress a bit.
As a Rubyist and someone that used Jekyll before, the choice was obvious. I want to be able to tweak my sites with Ruby. If you don’t like Jekyll in particular, try Hugo.