Getting Started with Jekyll

intro_to_jekyll_header

Once upon a time, the web was mostly static sites. We built our sites with Dreamweaver or Homesite (or -gasp!- FrontPage) and manually updated them to add new content. Creating new pages was often a matter of copying an existing HTML file and modifying it. This was fine for very simple sites, but could be painful to manage for large sites.

The static site engines of today sit somewhere in between the manual updating of the early web and the content management systems that often replaced them. They make it relatively easy to add and update content, relying on files to store content and data as opposed to databases. This allows the site to be built dynamically but deployed statically. Paired with the growth in services that can add the necessary dynamic pieces to the site (like comments, forms, etc.), static sites have become a viable solution for a large number of sites, especially those that are content-focused.

Probably the most popular static site engine is Jekyll. It was created by Tom Preston-Werner, founder and former CEO of GitHub, and is the engine frequently used for running GitHub Pages. In this article, we’ll look at how to get started using Jekyll and build a simple site with it.

The Sample

I’m a fan of the Cartoon Network show Adventure Time! The sample site we’ll use is essentially a very simple Adventure Time! fan site. You can see a glimpse of what the sample looks like below:

Sample Adventure Time! Site

While the site is intentionally simple for example purposes, it offers the opportunity to explore a number of important features beyond the basics of templating and content creation:

  • Custom global metadata – the ability to set custom metadata global to the site that can be accessed and utlized in templates;
  • Custom post metadata – the ability to set custom metadata on a per post basis that can be accessed when listing the post or in the display of the post itself;
  • Data sets – the ability to add content that is not a post or page, but rather a custom data type (in the case of this sample that is the character data).

You can pull the full source of the example (plus alternatives built using other static site engines) on GitHub.

All the character data, content and images used in the sample are from the Adventure Time! wiki. The design was based upon a free template from HTML5UP (because you don’t want to see what this would look like if I designed it myself).

Getting Set Up

Jekyll is built on Ruby. Installing it on OSX is easy.

sudo gem install jekyll

Jekyll on Windows

Unfortunately, Jekyll is not officially supported on Windows. However, the official documentation does link to a walkthrough on how to get Jekyll running on a Windows machine. While not as simple as the one command install on OSX, I can confirm that it worked for me on a Surface Pro running Windows 8.1.

Creating a Site

To start a new site using Jekyll, simply enter the following command:

jekyll new [project name]

For the example site, we just gave the project a name of “jekyllsite”, so the command is jekyll new jekyllsite. This will generate a folder with the given project name that includes bunch of files that we can modify to start building our Jekyll site.

One important aspect to understand within these generated files is that any file or folder that is preceded by an underscore (ex. _layouts) does not create a corresponding file or folder when the code is generated. This means, for example, that when you build and deploy the static files there will be no _layouts folder.

Testing Your Site

To test these the starter site, simply change directory into the project folder and fire up the Jekyll local server.

cd jekyllsite
jekyll serve

The local server runs on port 4000 by default (though this is configurable with the -P option), so you can access your page by opening http://localhost:4000 in your browser. The Jekyll server has a number of potentially useful options such as -w to watch for changes and automatically rebuild, -t to show a full backtrace when an error occurs or -V to enable verbose output. For a full list of server options use jekyll serve -h.

Templating Basics

By default, Jekyll uses a Ruby templating library created by Shopify called Liquid. I say “by default” because Jekyll supports extensions and there are a number of extensions for supporting additional tempalating languages. However, Liquid itself is fairly easy to learn and quite powerful.

Your templates should be placed in the _templates folder. Let’s look at some of the basics. Again, we’ll be using the code from the sample available on GitHub.

Output Data

To output a variable using Liquid in your Jekyll templates, you simply wrap the variable name using curly braces. For example, the below HTML (from _layouts/page.html) is outputting the title of a page.

<h2>{{ page.title }}</h2>

If you’re wondering how you know what variables Jekyll makes available within your templates, here’s the list.

By default Liquid also offers a bunch of standard filters that can be extremely helpful for formatting data. For example, in the following example from index.html in the sample formats the post’s date using the date filter:

<p>Posted {{ post.date | date: "%b %-d, %Y" }}</p>

Jekyll actually expands on Liquid filters, adding some of its own.

Includes

Of course, when you are writing templates, you’re going to want to split up your files both to make your code more readable and easier to maintain, but also to allow you to reuse portions. This is easy to do using the include tag. Includes should be placed under the _includes folder.

The following code from _layouts/default.html includes the file _includes/header.html:

{% include header.html %}

Conditionals

You can wrap display elements in if/else (or elseif) to display it based upon certain conditions. For instance, in _includes/header.html, we only want to display the banner if we are on the home page:

{% if page.url == "/index.html" %}
<!-- Banner -->
    <section id="banner">
        <header>
            <h2>Explore the Land of Ooo...</h2>
            <p>...and its many kingdoms!</p>
        </header>
    </section>
{% endif %}

Looping

Looping is something you will be doing frequently throughout your templates. Whether you are looping through your posts or looping through data from a YAML/JSON/CSV file (I’ll discuss this in detail later in this article). This can be done with a for loop.

This example from index.html, we loop over an array of data to populate a part of the page that displays Adventure Time! characters. It passes each item in the array as an object in the variable character. Within the loop, we are outputting all of the properties of the character object.

{% for character in site.data.characters %}
    <div class="4u">
        <section class="box">
            <span class="image featured"><img src="{{character.image}}" alt="" /></span>
            <header>
                <h3>{{character.name}}</h3>
            </header>
            <p>{{character.description}}</p>
        </section>
    </div>
{% endfor %}

We can also limit how many iterations in a loop are allowed. The following example, also from index.html, shows only the first two posts using limit.

{% for post in site.posts limit:2 %}
    <div class="6u">
        <section class="box">
            <a href="{{ post.url | prepend: site.baseurl }}" class="image featured"><img src="{{ post.banner | prepend: site.baseurl }}" alt="" /></a>
            <header>
                <h3>{{ post.title }}</h3>
                <p>Posted {{ post.date | date: "%b %-d, %Y" }}</p>
            </header>
            <p>{{ post.excerpt }}</p>
            <footer>
                <ul class="actions">
                    <li><a href="{{ post.url | prepend: site.baseurl }}" class="button icon fa-file-text">Continue Reading</a></li>
                </ul>
            </footer>
        </section>
    </div>
{% endfor %}

We can even start a loop with an offset. For example, the following code from _includes/footer.html starts at the third post entry and stops after five iterations.

{% for post in site.posts limit:5 offset:2 %}
    <li>
        <span class="date">{{ post.date | date: "%b" }} <strong>{{ post.date | date: "%-d" }}</strong></span>
        <h3><a href="{{ post.url | prepend: site.baseurl }}">{{ post.title }}</a></h3>
        <p>{{ post.shortdesc }}</p>
    </li>
{% endfor %}

Within loops you also have access to a number of variables to get the current iteration, whether this is the first or last iteration and so on. You also have ability to reverse a loop. Check the documentation for more details.

Creating Posts

Now that we’re comfortable building our templates with Liquid, it’s time to add some posts. By default, posts are written in Markdown, though other formats are supported by plugins.

If you aren’t familiar with Markdown, it’s very easy to learn and many code editors either support it by default or via free extensions. There are even a ton of standalone Markdown editors, for example I use Mou on OSX and there is MarkdownPad on Windows. We won’t go into detail about the specifics of Markdown here.

There are two important aspects to understand about posts. The first is that posts must be placed in the _posts folder and must be named using the format of year-month-day-title.markdown (or .md). For instance, a post from our example site posted on April 21, 2014 is 2014-04-21-season-6-escape-the-citadel.markdown. The title portion of the file name is up to you, and doesn’t actually have to match the actual title of the page in the metadata (we’ll look at that in a moment). This will be translated to /2014/04/21/season-6-escape-the-citadel.html when the final URL is created.

Front Matter

The other important aspect of creating posts is that every post must have “front matter“, which is YAML formatted metadata at the beginning of each post. Front matter can be added to any Jekyll file, but plays an important role in posts.

Front matter is identified by two sets of triple dashes (---) at the beginning of a post. While, technically speaking, front matter can be left empty, it generally is not for posts. In our examples, we include some of the predefined variables such as:

  • layout – this specifies which layout file (from _layouts) will be used when generating this post/page.
  • categories – this is a space separated list that allows you to place posts/pages in subfolders when the site is generated. For example, a post with categories: season6 episodes will be generated into /season6/episodes/. If you only have one category, you can alternately use just a category property.
  • date – this is the only predefined variable specific to posts and overrides the date that is parsed from the post’s file name. This can be useful for ensuring that posts are properly date-sorted by Jekyll.
  • title – while not technically listed as a predefined variable in the documentation, posts generally include a title property (in fact the default generated posts do) that you can access via {{ page.title }} when displaying the post.

We can add any arbitrary front matter variable we want as well, which we’ll discuss in more detail later in this article. It’s also worth noting that front matter can be added to anything, including templates or even CSS files.

Customizing Excerpts

When displaying posts or articles, you’ll often want to display an initial portion of text as an excerpt. Sometimes you’ll just want to grab the initial paragraph of text, but, most often, you’ll want to designate a break point where the excerpt should end to help ensure that it doesn’t exceed a particular length.

Jekyll gives you the ability to set an exerpt seperator. The _config.yml file is where we put Jekyll configuration. In our sample application, we chose a seperator of <--more--> by adding the following setting:

excerpt_separator: "<!--more-->"

The excerpt is available as a property on a post/page (from index.html).

<p>{{ post.excerpt }}</p>

If you want to disable the excerpt entirely, you can set it to an empty string. You can also manually set the excerpt by adding a property named excerpt to the “front matter” metadata on each post.

Custom Metadata

It is very common that you’ll have your own set of metadata, whether global to the site or local to a post/page, that you’ll want to add. Jekyll makes this very easy to do.

Global Metadata

Any variable that you set within the _config.yml in the root of your Jekyll site will be available throughout the site whenever the page is being processed. For example, let’s look at a portion of the configuration file from our sample site:

# Site settings
title: Adventure Time!
email: brian.rinaldi@gmail.com
banner: "/images/about.jpg"
description: > # this means to ignore newlines until "baseurl:"
  Adventure Time is an American animated television series created by Pendleton Ward for Cartoon Network. The series follows the adventures of Finn, a human boy, and his best friend and adoptive brother Jake, a dog with magical powers to change shape and grow and shrink at will. Finn and Jake live in the post-apocalyptic Land of Ooo. Along the way, they interact with the other main characters of the show: Princess Bubblegum, The Ice King, and Marceline the Vampire Queen.
baseurl: "" # the subpath of your site, e.g. /blog/

In the settings above, we have added a banner and description. These are accessible within the site object. For instance, we use these to populate a portion of the footer within _includes/footer.html:

<section>
    <header>
        <h2>What's this all about?</h2>
    </header>
    <a href="#" class="image featured"><img src="{{site.banner | prepend: site.baseurl}}" alt="" /></a>
    <p>{{ site.description }}</p>
</section>

Per Post Metadata

As was briefly mentioned earlier, we can add any arbitrary item to a post or page within the front matter. Let’s look at the front matter within the _posts/2014-06-12-season-6-food-chain.markdown file:

---
layout: post
title:  "Food Chain (Season 6)"
date:   2014-06-12 10:33:56
categories: season6 episodes
shortdesc: Finn and Jake learn about the food chain by becoming the food chain.
banner: /images/foodchain.jpg
---

As we discussed earlier, the layout, title, date and categories variables are standard post variables. However, we’ve added shortdesc (which is used for a very short description of the post) and banner (which adds a per post banner image).

These variables are, of course, accessible when outputting the display of a post, as in _layouts/post.html:

<!-- Content -->
<article class="box post">
    <div class="image featured" style="background-image: url('{{ page.banner | prepend: site.baseurl }}');"></div>
    <header>
        <h2>{{ page.title }}</h2>
        <p>{{ page.shortdesc }}</p>
    </header>
    {{ content }}
</article>

Just as importantly, we can access these when looping through a list of posts, such as in _includes/footer.html in our sample site where we utilize the short description (shortdesc) variable that was added:

<ul class="dates">
{% for post in site.posts limit:5 offset:2 %}
    <li>
        <span class="date">{{ post.date | date: "%b" }} <strong>{{ post.date | date: "%-d" }}</strong></span>
        <h3><a href="{{ post.url | prepend: site.baseurl }}">{{ post.title }}</a></h3>
        <p>{{ post.shortdesc }}</p>
    </li>
{% endfor %}
</ul>

Custom Data

Not all content on a site is a post, of course. Sometimes, you’ll want the ability to have other types of data that can populate sections of the site. For instance, on our sample site we maintain a list of Adventure Time! characters with names, descriptions and photographs. By placing these in a data file within Jekyll, we are free to output (and filter) them wherever and however we want, while maintaining only a single copy of the data. Let’s look at how this works.

Data files are placed in the _data folder in the root of your Jekyll site. Jekyll actually allows us to use YAML, JSON or even a CSV file to maintian the data. In the case of our character data, we’ll use YAML. Here’s a snippet from _data/characters.yaml:

-   name: "Finn the Human"
    image: "/images/finn.jpg"
    description: "Finn is a 15-year-old human. He is roughly five feet tall and is missing several teeth due to his habit of biting trees and rocks among other things."
-   name: "Jake the Dog"
    image: "/images/jake.jpg"
    description: "Jake can morph into all sorts of fantastic shapes with his powers, but typically takes the form of an average sized yellow-orange bulldog."

Any data files within the _data folder are processed by Jekyll and made available under the site.data object, using the file name (sans extension) as the object to access the data. For example, our character data is accessible under site.data.characters since the file was characters.yaml.

We can now use our data to loop through and access the character information and display it, as in index.html from the sample site.

{% for character in site.data.characters %}
    <div class="4u">
        <section class="box">
            <span class="image featured"><img src="{{character.image}}" alt="" /></span>
            <header>
                <h3>{{character.name}}</h3>
            </header>
            <p>{{character.description}}</p>
        </section>
    </div>
{% endfor %}

Our data structures are simple, but we’re free to add nested data structures if needed.

Jekyll also supports collections, which would have worked for our characters data just as well. You can see documentation for collections here.

Generating and Deploying

Now that’s we’ve built our awesome Adventure Time! fan site, it’s time to generate and deploy it. Actually, if you’ve previewed the site using Jekyll’s local server, Jekyll has already done a build for you. The generated files can be found in the _site folder. To manually build the site, simply enter the command:

jekyll build

This will regenerate all the files and place them in the _site folder. You can override this destination if you choose to using the -d option and specifying a directory where the generated site should be placed.

We’re ready to launch our site, all you need to do is open your FTP client and upload the generated files to our host!

Other Deployment Options

There are a number of options for deploying other than manually uploading via FTP. For example, I’ve use a Ruby gem called Glynn which is designed for automate connecting to FTP and deploy a Jekyll site. It can easily be configured via the _config.yml.

If you want to host your site on GitHub Pages, there is a gem file available to make the deployment process easy (check Jekyll’s documentation on this topic here).

There are numerous other options, some of which are covered in the Jekyll deployment documentation. Keep in mind, of course, that the generated files are just static HTML, CSS and JavaScript, so you can deploy them just about anywhere without needing an integrated deployment solution.

Conclusion

Whether you are running a blog, an online magazine or a company web site, static sites are a viable option, especially if your site is content-focused. Jekyll makes it relatively easy to build static web sites that are easy to update and deploy. Hopefully this guide has piqued your interest and you’ll give Jekyll a try. If you do, please feel free to share your experiences in the comments below.

Header image courtesy of unicornlover69

Comments

  • Thanks for the article! Could you recommend some static site generator that is based on nodejs and/or Gulp? Would be good if it was compatible with the Jekyll structure.

    • remotesynth

      I’ve used both Harp and Wintersmith. They are both viable options, though not nearly as robust as Jekyll. Harp works better for sites rather than blogs. Wintersmith is powerful but, in my opinion, severely lacking in documentation. Is there a reason you need it built on Node? The end result is static either way.

      • I want to find a node-based engine because I don’t feel comfortable with Ruby. For example, I needed to add auto-prefixer to the pipeline but I didn’t figure out how to do it. I have added autoprefixer-rails and jekyll-assets gems but that didn’t work for some reason and I don’t have knowledge how to debug this. Ended up having a gulp task which runs after `jekyll build` and prefixes the resulting css.

        And because I use node/gulp every day it would be more comfortable to me.

        I will try Harp and Wintersmith, thanks!

        • In my almost 2 years using jekyll I have wrote (copied) maybe 5 lines of ruby code so it doesn’t force or require you to use it. In my set up I use grunt-jekyll https://github.com/dannygarcia/grunt-jekyll as it works well and allows you to use all your favorite libs. I have set up target “grunt prod” that uses autoprefixer, htmlmin, etc.

        • There’s no reason that you need to be stuck to one technology, I’ve written about using Gulp with Jekyll here http://steveedson.co.uk/gulp/advanced-tasks. But also, like Justin says, you don’t have to write any ruby to use Jekyll

          • Guest

            I’ve seen your article before and commented on it – a very good one! Actually, I’ve already migrated my blog to a custom gulp-based system that supports handlebars and markdown. Maybe I will publish it some day. In my opinion, it works better than Jekyll (of course, only for the subset of features I used) and I can finally remove ruby stuff from my laptop 🙂

            Most probably, I would borrow some tips from your blog post too! I am a fan of jspm as well and I plan to integrate it too. Btw, there is a JS api for JSPM so no need to call shell.

            Among features that I plan to add: switching of themes for highlight.js, offline mode (appcache + service workers), uncss and seo like you suggested in your post

          • Guest

            I’ve seen your article before and commented on it – a very good one! Actually, I’ve already migrated my blog to a custom gulp-based system that supports handlebars and markdown. Maybe I will publish it some day. In my opinion, it works better than Jekyll (of course, only for the subset of features I used) and I can finally remove ruby stuff from my laptop 🙂

            Most probably, I would borrow some tips from your blog post too! I am a fan of jspm as well and I plan to integrate it too. Btw, there is a JS api for JSPM so no need to call shell.

            Among features that I plan to add: switching of themes for highlight.js, offline mode (appcache + service workers), uncss and seo like you suggested in your post

          • Guest

            I’ve seen your article before and commented on it – a very good one! Actually, I’ve already migrated my blog to a custom gulp-based system that supports handlebars and markdown. Maybe I will publish it some day. In my opinion, it works better than Jekyll (of course, only for the subset of features I used) and I can finally remove ruby stuff from my laptop 🙂

            Most probably, I would borrow some tips from your blog post too! I am a fan of jspm as well and I plan to integrate it too. Btw, there is a JS api for JSPM so no need to call shell.

            Among features that I plan to add: switching of themes for highlight.js, offline mode (appcache + service workers), uncss and seo like you suggested in your post

          • I’ve seen your article before and commented on it – a very good one! Actually, I’ve already migrated my blog to a custom gulp-based system that supports handlebars and markdown. Maybe I will publish it some day. Works good and I can finally remove ruby stuff from my laptop 🙂

            Most probably, I would borrow some tips from your blog post too! I am a fan of jspm as well and I plan to integrate it too. Btw, there is a JS api for JSPM so no need to call shell.

            Among features that I plan to add: switching of themes for highlight.js, offline mode (appcache + service workers), uncss and seo like you suggested in your post

          • Oh I should pay more attention sorry! Yeah I’d love to find out more information on how thats working. I’m also starting to look into service workers, it’s the reason my site is now using SSL, as it is a requirement :).

          • I’ve seen your article before and commented on it – a very good one! Actually, I’ve already migrated my blog to a custom gulp-based system that supports handlebars and markdown. Maybe I will publish it some day. Works good and I can finally remove ruby stuff from my laptop 🙂

            Most probably, I would borrow some tips from your blog post too! I am a fan of jspm as well and I plan to integrate it too. Btw, there is a JS api for JSPM so no need to call shell.

            Among features that I plan to add: switching of themes for highlight.js, offline mode (appcache + service workers), uncss and seo like you suggested in your post

          • Oh I should pay more attention sorry! Yeah I’d love to find out more information on how thats working. I’m also starting to look into service workers, it’s the reason my site is now using SSL, as it is a requirement :).

          • I’ve seen your article before and commented on it – a very good one! Actually, I’ve already migrated my blog to a custom gulp-based system that supports handlebars and markdown. Maybe I will publish it some day. Works good and I can finally remove ruby stuff from my laptop 🙂

            Most probably, I would borrow some tips from your blog post too! I am a fan of jspm as well and I plan to integrate it too. Btw, there is a JS api for JSPM so no need to call shell.

            Among features that I plan to add: switching of themes for highlight.js, offline mode (appcache + service workers), uncss and seo like you suggested in your post

          • Oh I should pay more attention sorry! Yeah I’d love to find out more information on how thats working. I’m also starting to look into service workers, it’s the reason my site is now using SSL, as it is a requirement :).

        • There’s no reason that you need to be stuck to one technology, I’ve written about using Gulp with Jekyll here http://steveedson.co.uk/gulp/advanced-tasks. But also, like Justin says, you don’t have to write any ruby to use Jekyll

  • Pingback: Dew Drop – March 16, 2015 (#1975) | Morning Dew()

  • Pingback: Tweet Parade (no.13 2015) - Best Articles of Last Week | gonzoblog()

  • great primer

  • great primer

  • great primer

  • I think the templates should go into _layouts directory instead of _templates as mentioned in the starting.

  • Justin James

    I have a post on setting up Jekyll on Windows using Chocolatey at http://digitaldrummerj.me/blogging-on-github-part-9-installing-jekyll-on-windows/ . With one command it will install node, git, ruby, ruby devkit, python 2, pygments, and the ruby bundler gem.

  • Pingback: Static Site or CMS? -Telerik Developer Network()