Pure-Code to Jekyll Website Rebuild Guide

Last Updated: 2021-04-22

Hypertext Markup Language is for suckers

Anyone who has ever had cause to maintain a website in pure HTML/CSS knows the pain of having to create new content for that site. Everything is a slog, whether it’s maintaining a blog by adding new posts, updating items on a web-based resume, or adding new images to an online gallery of your photography work.

For this exact reason, a whole whose of utilities known as Content Management Systems (CMSes) has been created, allowing you to manage your website with fancy tools like online, wysiwyg editors, huge internal databases of images and posts, and plug-and-play template tools. Of course, these tools bear their own burdens. Some are proprietary to the company that’s also planning to sell you the hosting. Others are freeware, but are well-known and well-loved, making them targets for abuse. After all, a dynamic site manager is doing things in motion, and therefore subject to exploitation, which adds a burden on you to keep them up to date.

What if I told you there was a third path - a way to avoid the serious heavy lifting of individually adding <p></p> tags throughout your work - where the site remains statically generated, but the editing was quick and convenient, template-based, and highly customizable on the fly?

Introducing Jekyll

Jekyll is a static site generation utility written and maintained in Ruby, maintained under sponsorship by a team of volunteers and offered as freeware under an MIT License. It works on a premise of templates and posts, relying on a few key bits of dynamism to generate a static site full of your content based on the copy, templates, and data you feed it. There’s no underlying database, and depending on your use case, no need to even have Jekyll running on the server or exposed to the internet.

I started using Jekyll in 2018 as it was the then-defacto standard blog manager for use with Github Pages. Then, when it was time to rebrand as Arcana Labs and rebuild the site, a long-dormant quality of life project saw to it that I rebuilt the website entirely in Jekyll’s templating methodology to make adding to it infinitely easier than it had been on the old method of “nano, rsync, and patience”.

How It Works

Jekyll relys on templates known as layouts (in a dir called _layouts) to divine what exactly you are trying to do with a piece of content and take its own special variant of markdown files and transcribe them into browser-friendly HTML. It does this in one of two ways:

  • Using a theme defined in a ruby gem, or;
  • Checking the local _layouts directory for the appropriate template.

In addition, it exposes a logic markup known as Liquid to allow you to reference specific bits of data, being site-wide variables from your config file, or page-specific variables either baked into the jekyll logic like page.excerpt, or even specific variables you set in a special section of the file known as the front matter. For example, this page has a special variable set in its front matter called page.updated, which is a datestamp of the last time I updated the page.

Using the filters, variables, and control flow defined through liquid it’s possible to create startlingly dynamic content. For example, our blogindex page’s template is only about six lines long, using a control flow loop to reference information about every public blog post and generate itself automatically.

This guide is going to focus on the latter method - creating your own, idiosyncratic theme through local files. We’ll presuppose that you’re familiar with HTML and with the general concept of control flow concepts in order to approach the topic as generally as possible.

Using a Pre-Made Theme

Using a pre-made theme is fairly straightforward and falls beyond the scope of this guide. It is covered in detail here.

Step 0: Obtaining the Pre-Requisites

There are three principle prerequisites you are going to require for this project:

  1. A web server capable of hosting static content. I myself am partial to self-hosting with nginx, but I endorse no particular solution.
  2. A clear understanding of the site you are trying to build, and;
  3. Jekyll itself.

Regarding Jekyll, there are two ways to obtain and use it:

  • Directly installed on your development machine, which necessitates installing Ruby and the various gems used in Jekyll. The Jekyll team provides instructions on setting this up here.
  • There is a semiofficial/maintained jekyll container repo called jekyll/jekyll available through docker hub. This is the version I used and I will discuss particular considerations of its use in this way further on in the guide.

Regardless of how you choose to obtain jekyll, issuing the command jekyll new will cause it to generate the folder structure it expects under a given directory. This folder structure is as important to the operation of your site. Additionally, several other important files such as the config.yaml file will be generated at this time.

Step 1: Configuration

The file _config.yml exists to tell jekyll what to do. Mostly, this sets various properties of the site object, which you can reference in your liquid markup in your templates. However, in certain other cases, values from _config.yaml also control the operation of the generator itself, turning certain features on and off.

Jekyll provide their own documentation on this file, but for now I want to call out a couple of points which were not immediately obvious but which were useful for constructing the arcana labs themes.

Defaults

defauls allows you to generate certain scoped defaults and assign values in the form of key:value pairs throughout a site like so:

defaults:
  - 
    scope:
      path: ""
    values:
      background: "circular-837510_1920.jpg"
  - 
    scope:
      path: "_software"
    values:
      background: "card-catalog-194280_1920.jpg"
  - 
    scope:
      path: "_hardware"
    values:
      background: "battleship-389274.jpg"
  - 
    scope:
      path: "_illuminated-arcana"
    values:
      background: "clock-2050857_1920.jpg"
  - 
    scope:
      path: "_guides"
    values:
      background: "old-4996186_1920.jpg"

In this example I have used the defaults feature to set a default value for “background”, meaning I now only have to add that to a page’s front matter if I want to overide the default. This allows me to let the generator determine based on either a specific setting or the location of the page within the site what background image file to use.

Highlighter

Syntax highlighting - which I thought was baked in - is more or less defined at the theme level. If like me you are using a fully custom theme you will need to add a value to config.yml to tell it what to use for a syntax highlighter. The sane default is highlighter: rogue. This also requires that you obtain the default css for rogue which the Jekyll folks make available.

Config is loaded once per run:

If you are running jekyll in build --watch mode, changes you make to the config will not be referenced unless you bring jekyll down and bring it back up.

Step 2: Coming Up With Layouts

Jekyll leans on layouts and their properties of inheritence in order to generate a site. A good way to look at how this is calculated is this:

  1. Your content object - such as this guide - includes in its front matter a value called layout which tells Jekyll which layout to use.
  2. In our case, where we’re not looking at a gem-based theme, it checks _layouts for a .html file of the same name, ex guide.html.
  3. In our case, guide.html has the following content:
---
layout: default
---

<div class="content">
	<div class="contentItem">
	<h1> {{ page.title }} </h1>
	<p><em>Last Updated: {{ page.updated }}</em></p>
		{{ content }}
	</div>
</div>
{% include footer_guides.html %}

This tells jekyll a few things:

  • the layout value for guides is default.
  • the liquid tells it where to put the page title, that custom updated value, and, in the case of content, where to put the content of the post after the github-flavoured markdown body of the post has been parsed.
  1. A cycle of inheritence begins in this case. After the content has been fully processed for guides.html, it itself becomes the content variable passed to the template stored in default.html (because of that layout value in the template’s own front matter).

As you’ve probably inferred this allows you to do quite a lot of nesting as suits your needs. You could have a dozen variant project formats like project-with-video, project-with-embedded-audio-player, project-nomedia, all of which inherit elements from project, which inherits from default. This means a lot of your templates are going to be very minor instances of content wrapping, each feeding into more wrapping, until you have an entire page worth of content.

This inheritance process is very powerful and essentially allows you to make the output code as automated - or specific and hand-crafted - as you like. A general solution to breaking your existing HTML code into jekyll templates is well beyond the scope of this guide, but I do want to cover a couple of general tips.

  1. Use includes where appropriate. Like some web servers, such as Apache and nginx, jekyll allows the concept of includes. This means if you have an element that is going to appear on multiple layouts (but not universally) you can define it in one central location.
  2. Use a default.html template. Default is going to be used by default, but it’s also a good place to put your universal code - the logic to define twitter meta tags, links to your style assets and scripts, menus and navigation, etc etc etc. Anything that is going to be on every single page of your site should be in default.html, and your more specific templates should always inherit from default.html by having layout: default in their front matter.
  3. Control flow is your friend. I mentioned before the blog index. Every index page on each section of this site works in a similar way. The example below is the full template for the blog index and its dozens of posts:
---
layout: default
---

<div class="content">
	<div class="contentItem">
		<h1 class="title">{{ page.title }}</h1>
		{{ content }} :))))
	</div>
</div>
{% for post in site.posts %}
	<div class="content">
		<div class="contentItem">
		<h1><a href="{{ post.url | absolute_url}}">{{ post.title }}</a></h1>
		<p><em>{{ post.date }}</em></p>
		{{ post.excerpt }}
		</div>
	</div>
{% endfor %}

Step 2: Go Go Content

All content in Jekyll consists of documents of markdown-formatted data preceeded with a section of yaml or json front matter seperated from the content by a line of three hyphens immediately before and after it:

---
layout: guide
title: Pure-Code to Jekyll Website Rebuild Guide
updated: 2021-04-22
blurb: >- 
 Breaking down the what, why, and how of using a static site generator called Jekyll to create your small website.
 
---

Such front matter is important for two reasons:

  • Jekyll will only parse the content if its present, otherwise the exact content of the file is preserved without additional processing, and;
  • the Front Matter lets you set contextual flags and variables that might be idiosyncratic to your use case.

I can’t tell you how to generate your content, only that your imagination may go as far as it likes. The important takeaway, though, is that the content is written in markdown format. You don’t need to fuss around with lengthy tags when * or [link](tosomewhere) will do, which is a major bonus for content generation, editing, and maintainance.

Data, And Assets

There are two special topics I haven’t covered here in detail:

  • Assets, such as your images, scripts, CSS, and downloadable files, and;
  • Data, which allows Jekyll to reference data stored in a variety of static formats.

I haven’t covered these for two reasons:

  • The assets are implemented in a highly powerful way that I haven’t fully realized - instead I am statically linking to my assets which isn’t very interesting as guide material, and;
  • I haven’t yet implemented the things I wish to use the data functionality for.

Nevertheless these are two extremely powerful tools useful for semi-dynamic content and content update automation, and when I do get around to using them I plan to expand on this, either here or in an additional guide.

Step 3: Generating Content

I have not talked yet in great detail about the appropriate use for jekyll itself, apart from briefly referencing build --watch. It’s worthwhile to note that while Jekyll does have a serve function, that function does not suit the purpose of production web service and even the Jekyll developers do not recommend using this beyond testing.

To this end, I wanted to take a moment so share the system I have set up, and make a recommendation of my own. This is appropriate if, like me, you are too impatient to wait for docker to allocate its volume bindings every time you make an edit.

  1. Have a docker container running jekyll build --watch, such that its output at /srv/jekyll is bound to the local filesystem for your convenience. This will allow you to edit the site content files and layouts easily, and --watch ensures that jekyll will recompute the site as often as you save.
  2. Ensure that the docker container above exposes no network ports and is not directly accessible.
  3. Bind output_dir/_site as the output directory of your dockerized server, e.g. nginx at /usr/share/nginx/html.

It’s worthwhile to note that this guide does not cover the security considerations of hosting your own site. If you want to use a hosting vendor the same general rules apply - the content of the site, once generated, lives in the _site directory and is in a ready to host condition.

I hope you found this guide as useful toward your first foray into this topic as I'll be finding it to my next need to repeat the process. Much like the output of my other projects, the guides collection offered at Arcana Labs is freely available knowledge. If you enjoyed this guide and want to see more guides like it, have a trip over to the support page. See an error? Something unclear? Contact me!