Jekyll: Sites Made Simple
About the Author
Simon Pascal Klein
Pascal is a standardista graphic, web and front-end designer, and a rampant typophile. Born in Mainz, Germany -- the birthplace of Gutenberg -- he now works in Canberra as a contract designer and studies at the Australian National University. He's been actively engaged in the Open Source community and local web industry, notably as one of the unorganisers to first bring BarCamp to Canberra.
He enjoys drinking in as much good type as he can get and has proudly bending beziers since 2004.
November 26th, 2009
Just this last August, I decided to relaunch my web site; I chose to give WordPress the flick and try out Jekyll, a Ruby-based static site generator. Jekyll? In the documentation it’s described thus:
Jekyll at its core is a text transformation engine. The concept behind the system is this: you give it text written in your favorite markup language, be that Markdown, Textile, or just plain HTML, and it churns that through a layout or series of layout files. Throughout that process you can tweak how you want the site URLs to look, what data gets displayed on the layout, and more. This is all done through strictly editing files, and the web interface is the final product.
Jekyll is a blog-aware, static site generator. It uses a set of template or layout files, the associated CSS files, and plain text files for the posts. The posts can use the Markdown format or another similar markup language. Jekyll grabs the post markup and inserts it into the layouts, spitting out standard HTML markup—along with linked files (any CSS, JavaScript, images, and the like)—ready for you to upload to your web server. There’s no content management system, no database, and no specific language support on the web server required—your site is a collection of plain old static HTML files.
Switching back to static markup may at first seem like a reversion to the late 1990s. Yet Jekyll is a simple and elegant system that mimics the characteristics of a dynamic site—drawing content from a database and inserting it into templates via a CMS—without all that complexity. As a firm believer in the KISS principle (Keep It Simple, Stupid), I always try to find the right tool for the job, and Jekyll is a simple solution to a common problem: “I want my own customized blog.” It’s also flexible enough that it can easily be used for other tasks. For example, GitHub uses it to drive GitHub Pages.
This article describes using Jekyll to build a web site: from downloading it, setting up a development environment, and hacking up template files. I’ll conclude with a word on maintaining updates between your development instance (for example, new blog entries) and synchronizing them to your web server. Note that I’ve written this piece from a designer’s perspective. Although I had a number of technical hurdles to hop over, Jekyll remains rather simple; for instance, using Jekyll’s template system and Liquid tags is easier than hacking a design as a WordPress theme. Never fear, I intend this article to be as designer-friendly as possible.
Setting up a Jekyll blog is relatively smooth-going and boils down to a few straightforward steps.
Jekyll is written in Ruby, so you’ll need to install it. It’s as simple as downloading the latest Ruby version for your platform and following the installation instructions. The Ruby installation also includes the RubyGems packaging system, and allows you to download and install other Ruby libraries and programs. If you’re using Windows, choose the one-click installer, and make sure you tick the Enable RubyGems option when running the installer.
We’ll be fetching Jekyll from the Gemcutter RubyGem hosting repository, but we first need to install the Gemcutter program.
If you’re on Mac OS X or Linux, open a command prompt and enter the following commands:
$sudo gem install gemcutter⋮ Successfully installed gemcutter-0.1.7 1 gem installed Installing ri documentation for gemcutter-0.1.7... Installing RDoc documentation for gemcutter-0.1.7...$sudo gem tumble
The tumble command will simply make the RubyGems installer query the Gemcutter repository first, when downloading RubyGem packages. This ensures that Jekyll is downloaded from the Gemcutter repository.
Now we can download and install Jekyll:
$sudo gem install jekyll
If you’re on Windows you’ll be entering the commands at the Windows
command prompt ( >
and enter
cmd):
C:\>gem install gemcutterC:\>gem tumbleC:\>gem install jekyll
Jekyll, like all good software, is modular and does one task very well, relying on other programs and libraries for additional functionality—after all, why reinvent the wheel? This modularity also gives us choice: you can pick and choose from a variety of slightly different tools that perform similar tasks to extend Jekyll, depending on your preferences. For example, if you’d rather write your blog posts in Textile instead of Markdown, you can. For me, however, the default (Markdown) does the trick, and Gem only needs a small number of other dependencies, which are fetched automatically.
For a full list of available gems that extend Jekyll, see the list within the install instructions for Jekyll on GitHub.
Now with Jekyll installed let us set up the file system structure.
First, create a new directory—placing it within your user directory is
fine. I put mine in ~/Sites/klepas. Move into this
new directory and create a few subdirectories and some files:
a plain text file named
_config.ymla directory named
_layoutsa directory named
_postsa file for our home page:
index.html
That done we can execute Jekyll by issuing the
jekyll command at the command prompt. You’ll notice
that Jekyll creates a directory _site, which now
holds an index.html file. If we had our web site and
configuration files set up at this point, our finished web site would now
be built and ready in the _site directory.
Let’s examine this file structure in closer detail:
_config.ymlAs the filename suggests, this is where your Jekyll configuration lies. It’s typically only a few lines long and allows you to avoid having to specify optional flags every time you run Jekyll from the command line; just stick them into
_config.ymland Jekyll will use them every time it’s run. For more info on Jekyll configuration, see the configuration page on the Jekyll wiki._layouts/The
_layoutsdirectory holds the web site templates, known as layouts in the world of Jekyll. Layouts use the Liquid templating language. When Jekyll is run, your posts are injected into the layouts using the Liquid tag:{{ content }}. Layouts are called upon at the beginning of each post in a string of variables called the YAML front matter that direct how Jekyll will process the file. This allows you to select a different template for each blog post or page as desired._posts/As the name suggests, this is where you keep your posts. Posts are plain text files that are named with the format
$YEAR-$MONTH-$DATE-$TITLE.$FORMAT. So, for example, I have a post from 25 October 2008 titled “Whose Garamond is it anyway?” which would sit in the posts directory as2008-10-25-whose-garamond-is-it-any.markdown. It ends in.markdown, but if you use Textile your post filenames would end in.textileinstead.
So, let’s create a few layouts and some posts. First, the layouts.
You can find instructions for migrating from an existing blog—Wordpress, Moveable Type, and others—in the Jekyll documentation on GitHub.
We’ll create two templates for us to pick from:
base.html, which is our base layout and
post.html, which extends the base layout and is
used for posts.
The base layout includes the HTML doctype, the <head> tag, and the container <div> tag that contains the Liquid tag
{{ content }}:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xml:lang="en" lang="en">
<head>
<title>{{ page.title }}</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<meta http-equiv="content-language" content="en" />
<link rel="stylesheet" href="/css/screen.css" type="text/css" media="screen" />
<link rel="stylesheet" href="/css/print.css" type="text/css" media="print" />
</head>
<body>
<div id="container">
{{ content }}
</div>
</body>
</html>
As this is the base layout, everything except the contents of the
container div will appear on all web
pages of your Jekyll site. This becomes even more interesting when we
add layouts that extend the base layout. Our post layout,
post.html, does just that:
---
layout: base
---
{% include header.html %}
{% include nav.html %}
<div class="entry-content">
<h2>{{ page.title }}</h2>
<span>Published: {{ page.date }}</span>
{{ content }}
</div>
In the opening few lines of the post layout file, we included a
YAML front matter note that defines this layout as an extension of the
base layout: layout: base. This means the contents of
post.html will be pulled into the container
div of the base layout thanks to the
{{ content }} Liquid tag. This is also true for all
other pages: index.html,
about.html, archive.html, and
any others as desired.
You may have spotted the two uses of the include directive at the start of the post template:
{% include header.html %}
{% include nav.html %}
Missing from the minimal file structure example above is the
_includes directory, which holds little content
snippets that you can reuse in layouts and static pages. They could be
the site navigation, the header, the footer, or even a very small piece
of content like a call-to-action message that you want to include in
more than one layout. Using the two Liquid tags above, I’m telling
Jekyll to fetch and include the contents of
_includes/header.html and
_includes/nav.html into my post layout upon site
generation.
When creating individual post files you also include a YAML front matter note. You can specify whichever fields you like—a title or date, for example—and those values will be available in your layouts. If you specify a layout it will be used for the post. For example:
--- layout: posts_xmas-theme title: Merry Christmas! --- ⋮
The above post will be displayed using a special Xmas theme layout
called posts_xmas-theme, and has a title value that
can be output in a layout as {{ page.title }}.
If you’re worried about permalinks at this point, Jekyll has it under control. The documentation shows how to specify the appearance of your permalinks in the _comfig.yml file.
The Liquid templating language is developed by the folk over at Shopify, the ecommerce system. It was designed to allow designers and front-end developers to tweak their shopping interface without affecting the security on the server they’re built on.
Liquid is quite simple to learn: there are two types of markup within Liquid, output and tag. Output markup (which is output to text) is enclosed by double curly brackets (or braces), like so:
Hello {{ name }}
Hello {{ user.name }}
Tag markup (statements that are not output as text) is enclosed by matched pairs of braces and percent signs, like so:
{% if user.name == 'tobi' %}
Welcome, Tobi!
{% elsif user.name == 'bob' %}
Go away, Bob!
{% endif %}
Tag markup allows if … else operations,
for loops, variable assignment, and more.
Output markup also allows you to filter the output through Liquid filters; for example, to change text to uppercase:
Hello {{ 'tobi' | upcase }}
To change a date to a different format, you can do the following to output a date like “November 24, 2009”:
Hello {{ now | date: "%B %d, %Y" }}
Liquid supports a broad range of filters, including date
formatting, capitalization, array element selection, markup manipulation
(for example, strip_html which strips HTML from a
string), replace functions, truncate functions, and simple mathematical
operations like addition, subtraction, multiplication, and division.
Jekyll
extends these filters with a few more of its own.
For more information on Liquid output and tag markup, see the Liquid for Designers documentation page on the Jekyll wiki on GitHub.
You’re probably now wondering how you create pages like the home page and archives page.
The index.html file you created earlier is
essentially your home page. All other .html and
.markdown (or .textile, and so
on) files in the root directory will also be processed by Jekyll. For
example, if you create a file called about.html in
the root of the Jekyll file structure, it’ll be placed in the root of
the generated site and could serve as your About page.
Pages like the index page can be entirely what you want them to
be. You could, for example, pull in the latest three posts, with the
latest featuring a large heading and the first 120 words styled in an
eye-catching manner, with the other two posts appearing smaller and
alongside each other, below the latest. The following is an example
index.html file; it opens with a title and a
template specified in YAML, and is populated using a range of Liquid
extensions to pull in content from the posts directory or
otherwise:
---
layout: base
title: The Title of Your Web Site
---
{% include header.html %}
{% include nav.html %}
{% for post in site.posts limit:1 %}
<div id="container">
<div class="page-nav-top">
{% if post.previous %}
<span class="page-nav-item">
<a rel="prev" href="{{ post.previous.url }}/"
title="View {{ post.previous.title }}"
>← View previous article</a>
</span>
{% endif %}
{% if post.next %}
<span class="page-nav-item">
<a rel="next" href="{{ post.next.url }}/"
title="View {{ post.next.title }}"
>View next article →</a>
</span>
{% endif %}
</div> <!-- /.page-nav-top -->
<div class="entry-content">
<h2 class="clear">
<a href="{{ post.url }}/" title="{{ post.title }}">
{{ post.title }}
</a>
</h2>
<span class="date"
title="{{ post.date | date_to_xmlschema }}">
<span class="published">Published: </span>
<span class="day">{{ post.date | date: '%d' }}</span>
<span class="month">
<abbr>{{ post.date | date: '%b' }}</abbr>
</span>
<span class="year">{{ post.date | date: '%Y' }}</span>
</span>
{{ post.content }}
</div> <!-- /.entry-content -->
</div> <!-- /#container -->
{% endfor %}
{% include footer.html %}
As you can see again there are a number of includes for the site header, navigation, and at the end a footer. The main part of the template is a loop:
{% for post in site.posts limit:1 %}
This tells Jekyll to fetch the posts in the
_posts directory, limiting the results to one: the
latest post.
Having selected only a single post, we can create chronological
“Next” and “Previous” navigation using the values
post.previous and post.next. These
values will be true if a previous or next post—chronologically
speaking—is available. If they’re available, we extract the URL and
title to output a link.
The post.previous and
post.next tags are currently
undocumented, but work perfectly.
The latest post’s title is output with
post.title and the link is created using
post.url. Every post has a date, so we create a
little microformat goodness using post.date. Finally,
and quite simply, we output the post’s body with
post.content.
An archive page is also easy to build:
---
title: Archives
layout: base
---
{% include header.html %}
{% include nav.html %}
<div id="container" class="archives">
<div class="index">
<h2><em>Notebook archives</em>…</h2>
<ul>
{% for post in site.posts %}
<li>
<a href="{{ post.url }}/#notebook" title="{{ post.title }}">
<span class="date">
<span class="day">{{ post.date | date: '%d' }}</span>
<span class="month"><abbr>{{ post.date | date: '%b' }}</abbr></span>
<span class="year">{{ post.date | date: '%Y' }}</span>
</span>
<span class="title">{{ post.title }}</span>
</a>
</li>
{% endfor %}
</ul>
</div> <!-- /.index -->
</div> <!-- /#container.archives -->
{% include footer.html %}
This time we use the same for loop {%
for posts in site.posts %}, to grab all the posts. Within the
loop we create list items for each post and output the posts’ titles and
dates. That’s all there is to it.
Apart from the specific directories and files mentioned above, all
other directories and files are handled by Jekyll as expected and will
be included in the generated site build in _sites/
when Jekyll is run. Thus, a css/ and
js/ directory and its contents, a favicon, and
whatever else will all be added to the site build.
Jekyll comes with a simple web server that allows you to pop over to
http://localhost:4000/ and view your site. To enable the web
server append --server when executing Jekyll:
$ jekyll --server
Even better, if you’re actively updating and making changes, a
useful flag to append to the jekyll command is
--auto. It will update the contents of the
_site directory automatically for you when a file
within the Jekyll structure changes:
$ jekyll --server --auto
You can also add auto: true to the configuration
file so that you can avoid having to type it in every time.
If you want to track your Jekyll files in a version control
system, it’s recommended to make an exception for the
_site directory. That’s because the files in the
_site directory are bound to change a lot,
particularly during the development phase.
Deploying your generated static site is just a matter of copying the
output in _site to your web server. You could use FTP
to upload your files, but there are a number of automated methods that
make life easier, particularly when you just want to upload the new
changes since the last upload.
My preferred current method is to use rsync, a
little UNIX utility written by Canberra locals Andrew Tridgell and Paul
Mackerras. rsync synchronizes data from one location to
another, and in doing so only sends changes rather than full files where
possible. You could either run this each time you update your site, or via
a
task script:
$ jekyll && rsync -avz --delete _site/ username@server.com:path/
There are a range of other, more complex automated methods, but they’re beyond the scope of this article.
As a static site generator without any dynamic extensions means Jekyll has no comment support. There are methods of adding commenting functionality via a third party or similar commenting service, such as DISQUS Comments.
I’ve personally decided to avoid comments; besides avoiding having to deal with them at all (code-wise, styling-wise, spam-wise—yay!), the best feedback I’ve ever received were face-to-face or directly via email. I also echo some of the sentiments raised by Alex Payne in his article, Why I Don’t Allow Comments, and More on Everything Buckets in regards to fostering a higher quality discussion.
Despite blog CMSs as powerful as WordPress, Movable Type, and so on, I hope you can now see why it’s worth reverting to a simplistic and rudimentary system like Jekyll: you use the right tool for the job. Jekyll is a breeze to pick up; its learning curve is certainly less steep than say theme design or hacking for WordPress, in my experience.
With GitHub Pages using Jekyll, it's common to see many Jekyll users sharing their Jekyll blog sources openly via GitHub. There’s a listing of Jekyll-generated sites in the documentation, along with links to their respective GitHub repositories when available. These are great to browse through for some knowledge on how other Jekyll users have built their web sites.
Learning curves aside, if you’re after a simplistic blog tool construction-wise that does not require multiple users or a fully featured web publishing interface, then Jekyll could easily do the trick. (Be assured, the sophistication of the final output for Jekyll can generate beautiful multi-layout blogs akin to WordPress.)
Ultimately, I changed to Jekyll because WordPress seemed like an overkill and I was up for learning a new platform. I hope this article serves to help you if you’re experiencing a similar predicament.