Jekyll, a static site generator

Published • 01 Nov 2010

When 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 is 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 is 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 customised blog.” Jekyll is 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 conclude with a word on maintaining updates between your development instance (for example adding new blog entries) and synchronising them to your web server. Note that I wrote 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. 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.

Setting up your development environment

Jekyll is written in Ruby, so you will need to install it. It is 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 are using Windows, choose the one-click installer, and make sure you tick the Enable RubyGems option when running the installer.

We will be fetching Jekyll from the Gemcutter RubyGem hosting repository, but we first need to install the Gemcutter program.

If you are 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 are on Windows you need to be entering the commands at the Windows command prompt (Start → Run… and enter cmd):

C:\> gem install gemcutter
C:\> gem tumble
C:\> 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.

Creating the basic file structure and some posts

Now with Jekyll installed let us set up the file system structure. First, create a new directory for you to work in. I put mine in ~/Sites/klepas. Move into this new directory and create the following subdirectories and files:

  • a plain text file named _config.yml
  • a directory named _layouts
  • a directory named _posts
  • a file for our home page: index.html

That done we can execute Jekyll by issuing the jekyll command at the command prompt. You will notice that Jekyll creates the 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 us examine this file structure in closer detail:

_config.yml

As 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.yml and Jekyll will use them every time it is run. For more info on Jekyll configuration see the configuration page on the Jekyll wiki.

_layouts/

The _layouts directory holds the web site templates, known as layouts in the world of Jekyll. Layouts use the Liquid template language. When Jekyll is run, your posts are injected into the layouts using the Liquid {{ content }} tag.

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 my posts directory as 2008-10-25-whose-garamond-is-it-any.markdown. It ends in .markdown, but if you use Textile your post filenames would end in .textile instead.

Creating layouts and posts

So, let us create a few layouts and some posts. First, the layouts.

We will 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 {{ content }} tag:

<!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 outlined 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 am 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 Christmas theme layout called posts_xmas-theme, and has a title value that can be output in a layout as {{ page.title }}.

Permalinks: if you are 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.

A short introduction to Liquid

The Liquid template language was developed by the folks over at Shopify, the e-commerce system. It was designed to allow designers and front-end developers to modify their shopping interfaces without affecting the security on the server they are 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, capitalisation, 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 Liquid wiki on GitHub.

Creating pages

You may probably be wondering now 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 will 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 }}"
              >&larr; 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 &rarr;</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 links 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 are available we extract the URI and title to output a link.

No documentation: the post.previous and post.next tags are currently undocumented, but work perfectly.

The latest posts 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.

Similarly, 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 is all there is to it.

Other directories and files

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.

Testing your site locally

Jekyll comes with a simple web server that allows you to point your web browser 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 are 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.

Version control: if you want to track your Jekyll files in a version control system, it is recommended to make an exception for the _site directory. This is because the files in the _site directory are bound to change a lot, particularly during the development phase.

Deploying your web site

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 synchronises 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 are beyond the scope of this article.

Comments… or lack thereof

As a static site generator without any dynamic extensions Jekyll has no support for adding comments dynamically. There are methods of adding commenting functionality via a third party or similar commenting service, such as DISQUS Comments.

I have 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 have 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.

In closing…

Despite blog CMSs as powerful as WordPress, Movable Type, and so on, I hope you can now see why I found it worth reverting to a simplistic and rudimentary system like Jekyll: 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.

Migrating from your existing blog: you can find instructions for migrating from an existing blog — Wordpress, Moveable Type, and others — in the Jekyll documentation on GitHub.

With GitHub Pages using Jekyll, it is common to see many Jekyll users sharing their Jekyll blog sources openly via GitHub. There is 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 insight on how other Jekyll users have built their web sites.

Learning curves aside, if you are 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. And 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 something new. I hope this article serves to help you if you are experiencing a similar predicament.

This article was originally published on SitePoint.com — Jekyll: Sites Made Simple. I wanted to update and re-publish it here for archiving and such that others could potentially stumble upon it.

For referencing: permalink to this article.