An overview on static websites with Jekyll

It’s a common practice starting a custom blog using one of the hundred CMS/CMF available out there. Wordpress, Drupal, Joomla are just the most popular names, and all of them share interesting features like:

  • working well out of the box;
  • customizations through external modules;
  • no programming skills required (at least for basic usage);
  • a wide selection of themes;
  • a large community ready to help.

Unfortunately their flexibility make them cumbersome, and when speaking about simply websites this is a double-edged sword, that’s why an overview on the difference between the two approaches is important to understand whether or not a simpler solution can fit our needs.

Dynamic platforms

CMSes are basically a bunch of code wrote in some language (PHP mostly), which rely on a database to store all the configurations and the data related to our posts, the pages are usually built on demand and cached in the same database to speed up the process.

To make use of these web applications the following steps are mandatory:

  1. Find a compatible web host service satisfying the minimum requirements requested by the CMS itself, mainly regarding the programming language and database versions;
  2. upload all the files in the remote host;
  3. import all the settings configured in the local database to the remote database or start to configure all the options directly from the remote host;
  4. populate it with our content;
  5. execute a periodic database and file backup from the remote host;
  6. execute a periodic update for the CMS platform and its modules in case of security advisors or general bugs to fix.

When a CMS version reaches its end of life, a more consistent upgrade is required to avoid further security issues which might leads to a probable data loss, and that involves:

  • a migration of the whole set of files;
  • a database migration of all the tables and records.

The level of automatizations for critical tasks, like an upgrade, gets improved version by version, but some error may always occur especially when the external modules are not properly tested or not available for the newer version.

This last case might delay the whole process when the module itself becomes unsupported or, in the worst case, abandoned.

Static website compilers

On the other hand a software like Jekyll is simply a generator which transforms a set of templates, settings and ASCII files to a bunch of structured HTML pages.

No databases, dashboards, graphical interactions, just you and a text editor to set up all the files required.

If it looks too elementary, probably it is, but at the same time its extreme simplicity is also its strength:

  • All the settings are configured in just one or a few structured files instead of navigating through several menus;
  • to setup your webserver you just require the generated files and probably some notions of .htaccess;
  • zero worries for security or malicious bugs in your code, it’s just HTML what you send to the remote server;
  • no database migrations in case you want to upgrade your version;
  • don’t need to upgrade unless you have free time or want to test something new;
  • do you want switch hosting? Just upload your files on the new remote hosting, fix the DNS if you have a registered TLD and it’s done.

Last but not least there are several areas where a static website is simply enough:

  • a simple blog;
  • an e-zine;
  • a photo gallery;
  • a catalog;
  • a showcase
  • a landing page.

A Jekyll overview

Jekyll is not the only static website generator, but for sure is the father of this kind of tools and introduced a new approach to arrange and better organize all the resources.

Several reasons pushed me to adopt it among the alternatives:

  • The wide and well-organized documentation available at the Jekyll website
  • it’s written in Ruby and uses familiar mechanisms like Bundler and Gemfile;
  • can be extended with several external plugins which cover many areas from SEO to better asset management, in general they are well maintained;
  • writing custom plugins on the fly is very easy and can be made, of course, in Ruby;
  • the wide amount of free themes available: just look on Google for free Jekyll themes and pick one;
  • YAML over XML for configuration files;
  • last but not least, Github offers free space to host static websites based on Jekyll.

To start with Jekyll is quite easy, first of all be sure to have Ruby installed:

$ ruby -v

then install the jekyll gem to the latest version (4.0.0):

$ gem install jekyll

or the last stable from the previous branch (3.8.6):

$ gem install jekyll -v "~>3"

The difference is in the number of tested and working plugins available, there is no reason to use the latest version if everything works fine with the previous stable branch.

To create the skeleton of our project:

$ jekyll new project_name

Foundamentally there are five types of content in Jekyll using their proper formats and folders:

  • data (_data folder);
  • pages (_pages folder or any other enabled folder);
  • drafts (_drafts folder);
  • posts (_posts folder);
  • collections (_collection_name folders).

And three ways to shape our information through templating or external code:

  • layouts (_layouts);
  • includes (_includes);
  • plugins (_plugins).

The formats used are YAML, JSON, CSV or TSV for data and Markdown for pages, posts, drafts and collections.

Layouts and includes are created using HTML mixed with the Liquid templating language, plugins are written in Ruby.

Data content type

In general any data file is a set of YAML files used to store structured information which can be used or reused in several parts of the website itself.

In practice these data files works the same way as child tables do within a database, you can change them once and update all the pages involved with the next compilation.

Some example of data file is:

  • a list of tags and categories (label, description, icon) referenced by a short string as key;
  • a list of links (label, url, title) used as a general menu;
  • the references of any columnist involved in our project (email, realname, role, personal_blog);
  • a glossary of terms key-based which can be reused within our posts (label, short_description, page_url);
  • technical data sheets about components frequently linked (realname, short_description, url);
  • extended bibliographies of frequently referred authors (realname, url) or books (title+, *authors or url).

Every information can be key based (dictionary or hash) or a simple ordered list (array).

Creating an include file for every data file is recommended but not mandatory, mostly depends on the nature of these data: when they have to be rendered in a single page, there is no reason to fragment our layouts, when they have to be rendered in different pages, it’s better to create a stand-alone code (includes).

Below an example with the menu.yaml file content:

---
- label: Home
  url: /
  title: Home page
- label: Search
  url: /search.html
  title: Search
- label: About
  url: /about.html
  title: About page

and the code used to render that data:

<ul class="menu">
{% for i in site.data.menu %}
<li><a href="{{ i.url }}" title="{{ i.title }}">{{ i.label }}</a></li>
{% endfor %}
</ul>

It uses the Liquid templating language to loop the site.data object where the items contained in the menu.yaml file are loaded.

Pages, Drafts, Posts and Collections

These four content have only the Markdown format in common, but serve different purposes.

Pages
Contents which have no relations with each other, let’s think to something like an about page, a contact us page, a 404 not found page, a rss feed page and so on, useful to have but not strictly related to the posts. Also a list of collections is a page.
Drafts
Unpublished posts still under work, useful to take note for articles that won’t be quickly released.
Posts
It is the main content of the blog, named also news, articles, or whatever it is the content that someone would expect to see visiting the website.
Collections
Groups of links or contents that don’t follow the publishing flow, they secondary for importance.

Let’s think to a blog about movies where the main goal involves publishing articles about reviews, opinions, and previews but everything focused on movies.

A nice addition might be writing some personal info about the most mentioned actors: their career, some gossip, or what they are working on.

Of course this information wouldn’t be the main subject of the website and will get poor updates, that’s why they will never appear on the main RSS feed as article, in that case introducing a biography collection might come in handy!

To do that we just create a subfolder named _biographies with a markdown file for each actor we want to mention.

Each file has fields and content in it:

---
name: Colin Farrell
birthday: 1976-05-13
url: https://www.imdb.com/name/nm0268199/
country: Ireland
---
He is simply awesome!

Then in the _config file we enable it:

collections:
  biographies:
    output: true
    permalink: /biography/:name

And here a page with the minimal code to list them all:

---
title: Biographies
permalink: /biographies.html
---
{% for actor in site.biographies %}
<div class="biography">
  <h3><a href="{{ actor.url }}">{{ actor.name }}</a></h3>
  <h4>Born the {{actor.birthday}} in {{actor.country}}</h4>
  <p>{{actor.content | markdownify}}</p>
</div>
{% endfor %}

Being all my pages organized in the _pages subfolder I specify the right path and name in the permalink key of the front matter.

Layouts, includes and plugins

Although the minima theme is provided by default, a common step is the creation of a more rich and visually attractive layout.

Mobile-first, fully responsive, Bootstrap or Foundation based, whatever is our goal, creating a custom theme for Jekyll is easy and straightforward thanks also to the Liquid templating language which puts some logic in the page rendering process.

In brief the Liquid language provides all the element to access configurations, posts, data and helper functions to transform the raw content in an HTML page, and makes use of two markers:

  • {{ }} the double curly brackets is used for print variable’s data, chaining filters in cascade with a pipe | operator can transform them;
  • {% %} the curly brackets and percentage symbol which introduces an action, like an iterative loop or a conditional if/then/else structure, over another group of instructions or data.

A layout is the structure of a certain page and can be included within another layout, creating a modular system in order to craft the several aspects of a website, every page or post can require a specific layout expliciting it in the front matter.

Usually under this folder may take place some or all these files:

  • default.html contains the general HTML structure, like the head and body sections, where the columns and the main menu take form;
  • feed.html define the XML structure of your RSS or Atom feed and is used exclusively by this page;
  • post.html this is the default layout for all the posts published and contains just the part which renders the title and the content, recalling default.html to complete the job;
  • page.html when a page layout is different from a post layout you want to define this too;
  • paginated_content.html is a special layout dedicated to the pagination of the contents and it is requested by the jekyll-paginate-v2 plugin which is a must-have unless you are hosting on Github.

An example for a default.html layout might be:

<html>
  <head>
    <title>{{ page.title | default: site.title | escape }}</title>
  </head>
  <body>
  <div>{% include top-menu.html %}</div>
  <div>
    {{content}}
  </div>
  <div>
    {% include bottom-bar.html %}
  </div>
  </body>
</html>

Includes files are another way to enhance the modularization inside a layout or a markdown content. They use a mix of HTML and Liquid templating language to render very small portion of code which can be easily embedded using the include instruction:

{% include <filename.html> <params> %}

It allows custom params in the format <key>='<value>' where the value might also be evaluated using the double brackets <key>={{ <variable> | <filter> | <filter> }} like in the example below:

---
layout: post
title: My summer vacations 2019
---
## Traveling to Rome

{% include gallery.html image="articles/my-summer-vactions-2019/1.jpg" %}
{% include gallery.html image="articles/my-summer-vactions-2019/2.jpg" %}
{% include gallery.html image="articles/my-summer-vactions-2019/3.jpg" %}

Plugins are the other way to create custom code to manipulate the data to render. Being Ruby the language used to create them, it is really easy to integrate other gems or grab and render data from an external API.

If at a first look plugins and includes might overlap their field of action let’s think to a collection of images having certain metadata set up, to extract that data and render them beside the image itself we have to create a plugin.

What I want to point out is that Liquid templating language is still a DSL language and for most general problems Ruby offers a more complete approach.

Final thoughts

In this article I shown the basics of Jekyll and the static website generators in general, why and when preferring this solution over more cumbersome CMSes, maybe in the next article I will try to lay down a list of interesting modules to build a real website.

For a deep look to all the topics faced in this article might be useful to give a look at the list of links below:


Creative Commons License This work is licensed under a Creative Commons Attribution-NoCommercial-ShareAlike 4.0 International License


Leave a Comment