Migrating from nanoc to jekyll
03 Feb 2013
Introduction
I had started with a website/blog last year and even took the effort to host it and enjoy the experience. Especially getting feedback in the comments, and learning that others are enjoying my notes, is great fun. I had started using nanoc
(here) as a generator for the site. It’s great, because it allows you to generate a static site from a set of source files. I love this, because it meets many of my criteria for a blogging engine:
- Static generation. Why waste server power generating a page on every view when it actually hardly changes?
- Modularity. Split form and content as much as possible.
- Flexibility. Ability to adapt to my wishes.
- Fun. Learn something new.
Now, I am moving away from nanoc
, and switching to jekyll
. Why? Because nanoc
is a little too complex for my taste. It can just do too much, and requires serious ruby
skills. Jekyll
is simpler, really made for much more simple blogging tasks that nanoc
is. After plugins and extensions have been added, the flexibility is good and the simplicity is still great.
Setting up Jekyll
Setting up jekyll
is easy. Just follow the instructions on the jekyll site, as easy as:
gem install jekyll
Note that I later switched to extended jekyll, see below.
The documentation for jekyll
is good, although structure is a bit lacking. If you read through this, it will be much clearer. Especially useful is studying the Liquid documentation and the Jekyll Liquid extensions.
Configuring happens through _config.yml
, and I have the following in there:
permalink: pretty # Required for the postfiles plugin
lsi: true # Will be useful for related pages later on
markdown: kramdown # I like kramdown
Switching to Jekyll_ext
In order to be able to extend jekyll
with stuff that will allow me to use Haml and Sass, I installed jekyll_ext
from github. Although I do call ejekyll
instead of jekyll
already, I am not at present using any of its functionality yet.
Adding jekyll-postfiles
One of the big troubles with jekyll
is that it does not have any concept of files that belong together with a post as a logical unit. This could mainly be relevant for images, but also stuff like downloads. I had started with one central subfolder in the jekyll folder. That works, but means that each post needs to link to this one folder, and that all the images are going to run together into one folder.
This is solved by the jekyll-postfiles
plugin by Andre Arko. Check it out. What it does, is allow you to put the files belonging to a post in the _postfiles/
folder, in a subfolder named like the post itself. So, the post _posts/2012-12-31-Test.md
would have a matching folder _postfiles/2012-12-31-Test/
where the matching files can be put.
Also, it gives you a Liquid tag that allows you access to the files from your post without needing to know where exactly the files are. It works like this:
{% postfile file.zip %}
Using Haml and Sass
Using Haml and Sass needs plugins that can get closely involved in the generation process. This cannot be done with the standard jekyll
plugins. There is another extension mechanism that can do it: jekyll_ext
. I am not yet using the tools that are based upon this, but will do in the future. I love Haml and Sass.
Migrating the layouts
I have several layouts in the site, and they turned out to need only small changes. Mainly, adjusting for a slightly changed folder structure and adding liquid tags instead of ERB code. All layouts except default
have a YAML Front Matter that points to the default
layout, which adds the navigation portion of the page and the whole HTML base of the page.
Layout: default
Adds the HTML boilerplate, CSS links and navigation to the site. Each page should end up passing through default
last. Because it is last, it does not have a YAML Front Matter.
For navigation links, I changed the hardcoded URLS in the original to the following Liquid structure. That should allow switching from the test environment to the production environment URLs easily (not verified yet).
<a href="{{ site.url }}/blog/index.html">Index of all articles</a> </li>
Also, Jekyll gives you the opportunity to access all the categories and tags in the site, so the navigation bar content changed to something like this (makes use of the category pages generated by the plugin mentioned below):
<H2>Categories</h2>
<ul>
{% for cat in site.categories %}
<li>
<a href="{{ site.url }}/categories/{{ cat[0] | downcase | replace: ' ','-' | cgi_escape}}">{{ cat[0] }}</a>
</li>
{{ }}
{% endfor %}
</ul>
Layout: Index
The index layout is really simple. It is meant for the main blog index page only. All it does is wrap the page content in <article>
tags and pass it on to default
. Easy.
---
layout: default
---
<article>
{{content}}
</article>
Layout: category_index
This layout is meant for the autogenerated category indes pages (see below). It again wraps the content in an <article>
tag, and then it loops through all articles in this category to list them one by one. I want to reduce the redundancy between this one and the content of my main index page (which is very similar), but that will be future work.
This is the entire thing:
---
layout: default
---
<article>
<h1 >{{ page.title }}</h1>
<ul >
{% for post in site.categories[page.category] %}
<indextitle><a href="{{ post.url }}">{{ post.title }}</a>
{% if post.indeximage %}
<IMG SRC="{{ site.baseurl }}/indeximages/{{ post.indeximage }}"/>
{% endif %}
<h2 class="itemdate">{{ post.date | date_to_html_string }}
<BR>
Filed under {{ post.categories | category_links }}</h2>
</indextitle>
<summary>
{{ post.summary }}
</summary>
{% endfor %}
</ul>
</article>
Layout: post
This layout is used for all actual blog posts. It lays out each article, and adds some comment stuff below that. This is how it looks:
---
layout: default
---
<article>
<header>
<hgroup>
{% if page.indeximage %}<img src="{{ site.url }}/indeximages/{{ page.indeximage }}">{% endif %}
<h1 class="itemtitle">{{ page.title }}</h1>
<h2 class="itemdate">{{ page.date | date_to_string }}</h2>
</hgroup>
</header>
<summary>
{{ page.summary }}
</summary>
<bodytext>
{{ content }}
<footer>
<p>
</p>
<div id="disqus_thread"></div>
.... Stuff from DISQUS ...
</a>
</footer>
</article>
Category navigation
In order to automatically generate category pages, I installed the plugin from this website. It works. That’s what is creating the navigation bar contents, seen above. It does replace spaces in category names by hyphens, so the above code does that, too.
I did encounter an issue with special characters in the category names (most notably spaces). I have blogged about the fix here.
A second issue, is that this creates lowercase category folder names, but my links in the navigation bar are with uppercase characters, too. So links break on the github
server, because it is case sensitive where my OS X is not. That’s why I added a downcase
to the Liquid
code in the default
layout.
Rakefile to make things more useable
The postfiles
plugin is great, but I do not like the ergonomics of creating the folders. The names are long, and I need to recognize all of it. So, I wrote a rake task to perform the duty of creating the postfiles folder for a specific post.
Assuming that the user usually wants to add a postfile to today’s post, the default behaviour is to make a folder for today’s post if rake postfile
is typed. In order to select another post, the user can search using rake postfile title="searchtext"
. If several hits are found, a menu is shown.
This is the part of the Rakefile
that does this:
Also, I stole parts of the Rakefile
from Jekyll-bootstrap. Especially, the post
and preview
commands I really like. I did edit them to meet my tastes, please see the file on GitHub.
Migrating the posts
As I had only a few posts, I decided to migrate them by hand. This involved:
- Moving the post file to the
_posts/
folder. - Renaming the file with the
yyyy-mm-dd-title.ext
pattern (I was previously using acreated_at
tag in theYAML
of the post). - Adding
layout:post
to the YAML front matter. - Copying the images to the
_postfiles/
folder (using my rake helper) and updating the references to the new Liquid tag. - Copy any index images to the
indeximages/
folder. - Testing.
Even though it was only two handfuls of posts, this turned out to be a bit tedious. Especially going through all the files for the internal links and referencing them to the correct place was a drag.
Old links
I have not found a way to keep my old links alive. Good thing I was giving DISQUS some unique IDs for the posts, at least that makes the comments still follow the posts to the new place. I could make dummy pages with redirects, but I am too lazy. This is not a high-traffic site, right?
Deploying to github-pages
The help for Github pages is quite clear. The trick was to migrate from my original repo to the new one, them being completely separate before. I will probably go back to the strategy that I followed for nanoc
.That is, to keep the source and output together in one repository with the source
branch containing the source and the master
branch containing the site itself. This does require some trickery in the Rakefile
. A drawback is that this deployment can break if git is not happy switching branches, but has the benefit that all files are kept together in the repo so all history is kept together.
First, I created the correct branch structure:
git checkout -b source
git checkout master
Then, I remodel the master branch to contain only the files from the _site folder. I edit the README file to make clear what’s going on. And I add a .nojekyll
file to tell Github not to try and process it itself. Finally, I make sure that the new source folder gets pushed to the origin (Github).
rm -rf xxxx yyyy zzzz # all files except _site/ and README
mv _site/* .
rm -rf _site
touch .nojekyll
vi README
git commit -A
git push -u origin source
Now, I need to come up with a Rakefile that does the correct processing, something like this:
- Check that I am on the source branch, or fail.
- Compile using
ejekyll
with the--url
option set for the server url. - Checkout master
Check out my latest Rakefile to check how I did it. Specifically, I made a deployment sub-rakefile in the _rake/
subfolder. Check it out if you want.