— Menu —

Hello!

I'm Keir Whitaker — I work in the Partnerships team at Shopify. You can my read latest blog posts, find out more about me & listen to the web industry podcast I co–host. You'll also find me on Twitter and Instagram.

Blog

Rebuilding keirwhitaker.com with Jekyll

Earlier this year I decided to have a look at Jekyll. Given that I am pretty well versed in Liquid, thanks to my Shopify work, and that you are able to host Jekyll sites on GitHub Pages for free it was pretty easy to set up a local site and try it out.

As I’ve discussed a few times on The Back to Front Show my day-to-day rarely involves working at the code face but I do like to keep my hand in and stay, relatively, up to date. Of course, Jekyll isn’t particularly new but it did mark my first foray into static site generation.

After some initial messing around, I decided to port this site over from the Slim PHP micro-framework to Jekyll. I’d been thinking about adding some kind of blog element back to my own site for some time but had resisted porting the site back to WordPress for the occasional blog entry — mainly due to not really having the time.

Jekyll gave me the option of creating a static site, optimised for speed, to which I could easily add in a blog without a massive undertaking. It also gave me the option of easily leveraging some semblance of data storage via data files. This ultimately proved very handy to manage my third party article and speaking lists.

Inevitably there were a few bumps along the way so I thought it would be worth sharing what I learned. Here’s what I’ll cover:

Setting up Jekyll

This was surprisingly easy. I followed the instructions on the Jekyll home page and was up and running within minutes. You’ll need to be familiar with the command line to get going quickly but the online instructions are very helpful.

The jekyll new command sets up a working site complete with layouts and most of the useful folders you will need. It did take me a while to get to grips with the folder structure but again the documentation proved very helpful in understanding what was happening.

One thing to get your head round is the _site folder. This is constantly updated with a fresh build of your Jekyll site thanks to the following command:

bundle exec jekyll serve

A quick side note on Bundler. Whilst I had used Bundler before it was something I needed to re-familiarise myself with. Essentially Bundler provides a consistent environment for Ruby (which Jekyll is written in) projects by tracking and installing the exact Gems and versions that are needed. It works hand in glove with the Gemfile located in the root of the project folder. Using Bundler effectively allows you to use different Gem versions for individual projects.

The above command, run via the Terminal from within your project folder allows you to view your Jekyll site locally. It also works to constantly monitor for changes to your site files and will regenerate your _site folder as changes occur. You’ll need to refresh your browser to see the changes — that is unless you are using an additional tool for browser refreshing.

This command will also manage Sass compilation. You can learn more about how Jekyll natively handles Sass here.

In the end, I chose to exclude the _site folder from my Git repository using .gitignore (more on this later) which also excludes it from view in Atom (the text editor I use) for development. Depending on your setup you may need to include this folder in your source control repository.

If you are using GitHub Pages you can omit it from your repository as the platform takes control of generating the site for you when you push your changes back to GitHub. This is usually the master branch of your repository but this, like most things, is configurable.

Porting Templates

After setting up the basic folder structure I set about creating the templates for the site using Liquid. This was probably the easiest part of the process and involved recreating the templates and replacing the Twig template logic with Liquid syntax.

Layout Files

This site runs off two basic layout files — a default and one specifically for blog articles. A layout file can also be seen as a master template that you can then plumb content into. It allows you to keep your site building DRY and allows you to update multiple pages from one file.

Knowing that I would likely need more than the standard layout file I chose to make use of numerous includes. An include is a small reusable piece of code that ideally will be used more than once. My usual rule of thumb is as follows:

Here’s my default layout file:

{% assign current = page.title | downcase %}
{% include html-header.html %}
{% include nav.html %}
{% include header.html %}
{{ content }}
{% if current != 'home' %}{% include newsletter.html %}{% endif %}
{% include footer.html %}
{% include html-footer.html %}

Here you can see there are six individual includes. Even though each include contains Liquid code they all have the .html extension. I’ll touch on extensions later in this article but unlike Shopify files generally have the .html extension in Jekyll.

You may also have noticed some other elements to the layout file. The first line helps me track which section is currently being viewed and this helps me use Liquid to apply a class to the menu navigation.

{% assign current = page.title | downcase %}

This line also makes use of the downcase Liquid filter which allows me to not worry if I accidentally forget the case in the individual templates front matter.

You’ll also notice a Liquid if statement. This simply allows me to position the newsletter signup form differently on the home page.

The final piece of the puzzle is {{ content }}. This placeholder is the area of the file where the appropriate template will be rendered. Unless you use Jekyll defaults you’ll need to specify this in your template files front matter — which we’ll look at later.

Routing

Jekyll makes routing, or creating your permalinks really easy. You can choose to nest files and folders in a traditional folder tree — this may prove useful if you are dealing with large file structures. Alternatively, you can make use of the Jekyll _config.yml file to configure certain paths as well as using individual files front matter where needed. This is the route I chose as I have relatively few pages to my site.

Jekyll also provides plenty of ways to configure collection permalinks. In most cases, we’ll be dealing with the _posts collection (i.e. the blog element) but the permalink structures can be applied to any Jekyll collection. We’ll look at the _config.yml file shortly but for reference here’s the relevant permalink section from this site.

collections:
  posts:
    output: true
    permalink: /blog/:title/

This sets up the default behaviours for the posts collection (blog articles). The two arguments work as follows:

output: true This will produce a file for each document in the collection and will create, by default, a URL structure based on the folder structure.

permalink: /blog/:title/ This will prefix your post with /blog/ and will append a URL-safe version of the document’s title. However, this can be overridden using the files front matter — which we’ll look at next.

This is explained better by example:

/_posts/2016-01-01-this-is-my-first-post.html

When generated Jekyll will allow this post to be available at the following URL:

/blog/this-is-my-first-post/

If you look at your _site folder you’ll notice that there’s very little magic going on here. Jekyll simply creates this URL by creating a folder with the title this-is-my-first-post and renders the post as index.html within it. This approach allows you to deploy Jekyll sites to any publicly accessible folder.

Front Matter

I’ve mentioned front matter a few times, here’s an explanation from the Jekyll docs:

Any file that contains a YAML front matter block will be processed by Jekyll as a special file. The front matter must be the first thing in the file and must take the form of valid YAML set between triple-dashed lines.

Here’s an example from this site:

---
layout: blog
title: How to Choose a Web Conference that's Right For You
published: true
featured: true
category:
  - web industry
---

Between these triple-dashed lines, you can set predefined variables (see below for a reference) or even create custom ones of your own. These variables will then be available to you to access using Liquid tags both further down in the file and also in any layouts or includes that the page or post in question relies on.

Many of the front matter variables are predefined and have particular reference to Jekyll. You can also create your own variables using non-reserved words. These are known as custom variables. You simply access these using the following syntax:

{{ page.customvariable }}

I mentioned the permalink structure for the _posts collection earlier and it’s perhaps worth talking about the opposite of a collection which is a page. If you are familiar with WordPress the analogy holds true for Jekyll. A collection file is normally date relevant whereas a page file is a stand-alone piece of content.

Jekyll will create page permalinks based on the path of the file relative to the project folder. For example:

/about/history/index.html

When rendered this would create a permalink of:

/about/history/

However you can override this default behaviour using front matter. Here’s an example:

permalink: /about-me/a-little-bit-of-history

If the permalink variable is specified Jekyll will ignore it’s default behaviour and create the following directory structure when generating the site:

/about-me/a-little-bit-of-history/index.html

You can also use Jekyll defaults to automatically specify things such as a posts layout file. This can be achieved using the following addition to the _config.yml file:

defaults:
  - scope:
      type: posts
    values:
      layout: blog

This will now mean that any addition to the _posts collection will automatically have the blog.liquid layout file applied. The layout can be overridden with front matter but this approach means it’s one less item to add for most use cases.

There are plenty of configuration options available in _config.yml depending on your needs.

Sass

After porting over the templates and layout files it was time to get my Sass file up and running. Whilst I am familiar with many of the great CLI tools for Sass I am quite happy using Codekit for my CSS and JS compilation and concatenation, and browser refreshing. In the end, I decided to take advantage of the inbuilt Sass compilation that ships with Jekyll so chose to exclude Codekit from dealing with Sass — which is straightforward.

The only addition I needed to make was empty front matter at the top of the .scss file as follows:

---
---
.is-wysiwyg
    font-size: 1.8rem

Jekyll treats Sass files as a regular page. This means that the output file will be placed in the same directory that it came from when the site is generated.

For example, if you have a file named css/styles.scss in your site’s project folder, Jekyll will process it and put it in your _site folder under css/styles.css.

Finally, it’s worth mentioning that you can configure how you would like your Sass output using options in the _config.yml file:

sass:
  sass_dir: _sass
  style: compressed

The Jekyll docs have an extensive page on how you can configure your setup. It’s worth reading as it also explains how to work with imports.

Blog

When I started to port the site over to Jekyll I hadn’t intended to add a blog feature immediately. I’ve tragically failed to keep up any writing schedule over the last year (bar the occasional Medium post) but after flip-flopping over where to write for far too long I decided to keep everything on this site and republish if I actually get my act together!

In order to create a basic article layout, I opted to create a new layout file as I wanted to include elements that won’t be relevant for any other aspect of the site.

Here’s the blog.liquid layout file:

{% assign current = page.title | downcase %}
{% include html-header.html %}
{% include nav.html %}
{% include header.html %}
<div class="l-content blog-article">
<div>
<div class="tag">Archive</div>
<h1>{{ page.title }}</h1>
<p class="article-date">{{ page.date | date: '%B %d, %Y' }}</p>
{{ content | markdownify }}
...
</div>
</div>
{% include newsletter.html %}
{% include footer.html %}
{% include html-footer.html %}

To help you understand the layout file I excluded the code that sits at the end of each article highlighting the category and an email link. Here it is:

<div class="blog-article-bio">
<p>This article was published by <a href="/about/">Keir Whitaker</a> on {{ page.date | date: '%B %d, %Y' }} in the <a href="/blog/categories/#{{ page.categories[0] | slugify }}">{{ page.categories[0] }}</a> category. You can view the <a href="/blog/">blog archives</a> and see a <a href="/writing/">full list</a> of my contributions to other publications. Discuss this article with me by <a href="mailto:[email protected]?subject=Blog Post: {{ page.title | xml_escape }}">email</a>.</p>
</div>

You can, of course, classify a post with multiple categories but I decided that for the most part, they will sit in a single category. To pull this out in the article footer I use {{ page.categories[0] | slugify }}. I pass this through the slugify filter as it enables me to create a URL-safe version for linking back to the blog category page.

Using markdownify

One big difference in this layout file is how the {{ content }} placeholder is handled. I compose posts in Markdown and it had been my understanding that these would simply be rendered as intended using {{ content }}. However, I ran into a few issues with this approach as my Liquid includes which passed in arguments were getting interpreted as Markdown and causing errors during compilation (I’ll talk more about this include shortly).

After reading up on the issue I discovered that filtering the posts using the markdownify Liquid filter solved the issue and allowed my include to work as hoped and the rest of the content be interpreted as Markdown.

Creating Blog Posts

Unlike hosted blogs Jekyll requires you to create your posts locally and then push the full generated site to your host — unless you are using a service such as GitHub pages which runs the site generation for you. Creating posts isn’t difficult but you do need to adhere to a file naming convention to make things work as intended.

To create a new post you need to create a file in the _posts directory and for it to be named according to the following format:

YEAR-MONTH-DAY-title.MARKUP

YEAR is a four-digit number — MONTH and DAY are both two-digit numbers — MARKUP is the file extension representing the format used in the file.

For example, the following are examples of valid post filenames:

2011-12-31-new-years-eve-is-awesome.md
2012-09-12-how-to-write-a-blog.md

However due to the fact that I wanted to make use of a Liquid include for images, I found that I needed to change the extension of my post files to .html.

As mentioned earlier my posts are written in Markdown. However, I found that using a .md or .liquid extension for the post files created site generation errors. After some searching and experimentation, I found the solution was to use a .html extension. It works, but I am not 100% sure exactly why.

Figure Include

I’ve mentioned that I make use of a Liquid include for images in posts, here’s how I reference it in the post files:

{% include figure.html url="/img/posts/conf-1.jpeg" alt="alt text" class="css-class" caption="caption text" %}

This highlights how you can pass in arguments to a Liquid include file. You can easily reference these in the include file using {{ include.variable }}, for example:

{{ include.alt }}

Here’s the include for reference:

<figure>
<img sizes="100vw" src="{{ include.url }}" alt="{{ include.alt }}" />
<figcaption class="{{ include.class }}">{{ include.caption }}</figcaption>
</figure>

You can, of course, use Markdown syntax to generate img elements — this is just an alternative approach.

RSS Feed

Given that I decided to add a blog back to the site I wanted to offer up an RSS feed (for those of us who still use it). Initially, I tried the jekyll-feed plugin. GitHub pages do support a number of whitelisted plugins including jekyill-feed but after testing it out I decided to hand crank the feed — to allow a little more flexibility.

I have Harry Roberts to thank for the feed template used on this site. I made a few minor tweaks but Harry’s template worked so well there was little reason to extensively modify it. The main change was adding in the markdownify filter again as follows:

{{ post.content | markdownify| xml_escape }}

The xml_escape filter appended at the end ensures that the XML won’t ”break” due to any special characters not being encoded.

Code Highlighting

The final challenge for the blog element was adding in the ability to create code examples. Luckily this is baked into Jekyll.

Jekyll has built in support for syntax highlighting for over 60 languages thanks to Rouge. To add a highlighted snippet you’ll need to use the following syntax in your post file:

{% highlight ruby %}
def foo
  puts 'foo'
end
{% endhighlight %}

In this case, we are specifying ruby as the language identifier — you can replace this with your desired language, e.g. CSS, Liquid etc.

Finally, you’ll need to reference a stylesheet (or copy and paste the classes into your own setup) which handles the actual output. I grabbed one from Ian Wootten’s jekyll-syntax GitHub repository and added it as an include to my .scss file.

Data Files

Initially I had planned on leaving the speaking and writing pages as flat hard coded .html pages within Jekyll. I don’t update them that often so I wasn’t sure refactoring was worth it. However, after reading up on the potential of data files I thought it would pave the way to make my life easier in the longer term.

In order to create data files, you’ll first need to create a _data directory in your project root. This is where you can store additional data for Jekyll to use when generating your site. These files must be YAML, JSON, or CSV files (using either the .yml, .yaml, .json or .csv extension).

During site generation, these variables will be available via the site.data.* where * is the name of the data file. This site has two data files:

external.yml stores data relating to articles I have had published on other sites

speaking.yml stores data relating to my workshops and speaking engagements over the last few year

Both have very similar structures, here’s an example of two entries in external.yml:

-   title: A Day with Shopify (MC)
    date: 2016-12-01
    city: Bristol
    country: UK

-   title: Shopify Theme Building Hints, Tips &amp; Tricks
    date: 2016-11-01
    city: Dublin
    country: Ireland

There are a number of ways to access this data in a template but for the most part you’ll do so via a Liquid loop. Here’s an example:

{% assign speaking = site.data.speaking | sort: "date" | reverse %}
{% for talk in speaking %}
<ul class="list-archive">
<li>{{ talk.title }} {{ talk.date | date: '%B %Y'  }} / {{ talk.city }}, {{ talk.country}}</li>
{% endfor %}
</ul>

Jekyll also allows us to sort data files according to a common variable. In the above example, I use the date variable. This took a little experimenting but by following the YYYY-MM-DD format I am able to add data anywhere in the file and let Jekyll will do the sorting for me.

You’ll notice that I am accessing the data via site.data.speaking. I am actually assigning the result of the sorted data (ordered by date) to a Liquid array called speaking and then using this variable in the Liquid loop.

I add one more filter to order reverse the chronological order so that the most recent is first:

{% assign articles = site.data.external | sort: "date" | reverse  %}

Config Files

You really can go to town on configuring your Jekyll install via a multitude of options. These options can either be specified in your _config.yml file, or can be specified as flags when starting Jekyll from the command line. I mainly use the _config.yml approach. I’ve highlighted a few already in this post. One worth looking into is exclude. This option allows you to, as the name suggests, exclude directories and/or files from the conversion. These exclusions are relative to the site’s source directory and cannot be outside the source directory. Here’s what I specify:

exclude:
    - Gemfile
    - Gemfile.lock
    - config.codekit3
    - "/js/libs"
    - "/js/site.js"
    - vendor
    - .sass-cahce

This ensures that working files for JavaScript and CSS aren’t added to the generated site.

I am not 100% up to speed on when you need to restart the Jekyll server after changing configuration options but have found that in most cases you’ll need to stop and restart the process in terminal using:

bundle exec jekyll serve

In the previous non-Jekyll version of this site I used a short PHP script to work out the active section of this site. By section I am referring to the URL element directly after the root, e.g:

Luckily this was pretty easy to replicate using the liquid operator contains:

<ul class="nav-main">
<li><a href="/"{% if page.url == '/' %} class="active"{% endif %}>Home</a></li>
<li><a href="/about/"{% if page.url contains 'about' %} class="active"{% endif %}>About</a></li>
...
</ul>

As you will see I don’t use contains for the home page as there’s really nothing to check for. Instead, I do a direct comparison of the page.url and /. For all other sections, I use contains to check for a keyword. If the result is true I add class="active" to the list item.

Deployment

Deploying your Jekyll site to GitHub Pages is very straightforward. Essentially any changes you push to the master branch will instigate a site rebuild. I mentioned earlier that you don’t need to add the _site folder to your repository as GitHub pages handles all this for you.

One thing I did choose to do was create a branch when adding in the blog facility as I wasn’t sure how long it would take me and I wanted to be able to make changes to the more basic site if needed. Once I had completed the work I simply merged the blog branch back into master which caused the site to be rebuilt.

For basic Git operations, I often use the GitHub for Mac app and occasionally revert back to Terminal if needed.

Checking GitHub for Errors

Occasionally I did push changes and couldn’t understand why the site hadn’t changed. After much digging around I found that you can also choose to have build errors emailed to you as well as online. You’ll find these notifications under your repositories settings tab. This was a case as “obvious when you know” but did stump me for a while.

Conclusion

Overall I’ve been really impressed with Jekyll. There have been a few “getting to know you” issues along the way but these didn’t take too long to iron out and get on track.

It’s certainly nice not having to worry about databases and setting up servers and the current free hosting via GitHub Pages is welcome. Of course, should they stop this, or reduce the offering somehow as I have heard mentioned, then moving to a different host is fairly simple and not as much of a headache as porting a CMS driven site.

So far I haven’t found anything that wasn’t possible but my site is hardly pushing the boundaries of content. I am sure a lot more is possible if I chose to push the _site folder myself as the plugin ecosystem is huge. For now, GitHub Pages does the job but we’ll see what the future holds.

Further Reading

This article was published by Keir Whitaker on January 05, 2017 in the Web Development category. You can view the blog archives, subscribe to RSS updates, and see a full list of my contributions to other publications. Articles are also availabe on Medium. Discuss this article with me by email.

Email Newsletter

Subscribe to Byte Size Bites

Byte Size Bites is a cultural dispatch for digital workers in email form. Topics covered include ecommerce, the web industry, podcasting, music, film, magazines, apps, gear, publishing, travel, remote working and more. Previous editions are archived on the Byte Size Bites web site.

100% no spam and you can unsubscribe any time!