James's Ramblings

Yet Another Blog Creation Article

Created: August 01, 2020

This blog is a static website created with Jeykll and hosted on AWS using Amplify. The version control system is GitHub and pushes to master automatically start a new Amplify build with GitHub Actions.

In this article, I’d like to share some insights on Jekyll and why doing a static website this way is so much better than doing it with a VM or Docker.

The Case for Jekyll

Jekyll is a static site generator written in Ruby. It transforms HTML, CSS, and Markdown into a static website. Additionally, Liquid, a templating language much like Jinja, can be used to make programmatic changes inside HTML and Markdown files.

Complex websites can be created with much less drudgery than writing using just plain HTML/CSS, and bloated, complex, slow, insecure, and time-consuming CMS solutions like WordPress can be avoided.

HTML markup can also be separated from the page contents and the page contents can be written in markdown. Very much reminiscent of the way XHTML was going 10 years ago, prior to HTML5 being a thing. Splitting HTML tags from the page contents means that many less HTML pages are necessary and writing/updating page content is much simpler as it’s in separate (easy to find) files.

This blog is currently sitting at around 50 pages but only one HTML file is used (with some Liquid) to generate it.

Furthermore, being markdown, much of the content is also perfectly usable inside a terminal emulator on my local machine.

The Case for AWS Amplify

AWS Amplify was extremely easy to set up and I have to say it saves a lot of time. I simply push my site to GitHub, then GitHub Actions sends it to Amplify, which builds it with a CI/CD pipeline. As my Jekyll install is simple, this required zero setup beyond specifying Jekyll as the software to use. Certificates are also maintained automatically with AWS’s ACME system.

There’s definitely a lot of value added there. There’s no need to maintain, secure, and monitor a web server; which is frankly quite a large undertaking to do properly. There’s also no risk of someone forgetting to refresh the SSL certificate, as so often happens.

This (or something similar) now seems the de facto way that static websites should be deployed. With Serverless it may even work for websites that require some level of dynamism.

The setup involved setting up a few DNS records (automated as I use Route 53), signing into GitHub through the AWS Console, adding a persistent GitHub Action for my repo, and some very simple setup in AWS Amplify itself.

Some gotchas are that you need to use (non-standard) alias records to point at an apex domain. That is to say if you want to use example.com for your website, you need to use AWS Route 53 or another provider that supports this non-standard record type. There’s only a small list of registrars.

As a consequence, you cannot use Cloudflare unless you point to a subdomain of the apex domain.

In the future, I will probably use GitLab’s CI/CD pipeline to replicate the process, host on an S3 Bucket, and point CloudFlare at it. The idea being CloudFlare will mitigate the risk of a high CloudFront bandwidth bill. However, learning AWS Amplify was definitely a worthwhile exercise and it is extremely simple for those of us that value time more than a higher bill.

Jekyll Tips

The Jekyll Documentation is pretty good and there is no point repeating effort. I would suggest starting there. I did find that the navigation isn’t great though; the answers you want might be there, just hidden somewhere on a page you can’t find (typically OSS perhaps).

Front Matter

Initially, I ran through the quick-start and then got stuck for 10 minutes wondering why my markdown pages wouldn’t display properly. Markdown requires some YAML called Front Matter at the top in order for Jekyll to use it.

Basic syntax looks like this:

---
title: "Something"
---
  • The title variable can be excluded.
  • More variables, including some custom values, can appear in Front Matter.
  • The Variables page is a useful reference.
  • Liquid can use variables.

Pages vs Posts

  • Pages appear in the root of the website directory or a subfolder. No special naming convention is required for these files.

  • Posts (aka traditional blog posts) appear in _posts and must follow the naming convention YYYY-MM-DD-title.{md|html}.

  • Pages and posts have distinct sets of variables

Gotcha: Changing _config.yml

Changing the _config.yml file requires Jekyll to be restarted in order for the changes to take effect.

Layouts (Overriding the Default Theme)

Layouts are where your HTML/CSS lives (unless you’ve built a custom theme). They live in _layouts.

The Liquid: {{ content }} tells Jekyll where you want to slap your content that lives in page/post files.

Layouts can be specified in Front Matter, like this:

---
layout: notes
---

However, that’s a bit tedious. In _config.yml, you can specify a defaults section to sort this out by path/type, like this:

defaults:
  -
    scope:
      path: ""
      type: "pages"
    values:
      layout: "default"
  -
    scope:
      path: ""
      type: "posts"
    values:
      layout: "default"
  • Note: editing _config/yml requires Jekyll to be reloaded.

Dynamically Generating Index Pages

One of the most tedious aspects of writing plain HTML/CSS is generating page links. With complex websites this quickly becomes unrealistic to maintain. Jekyll can generate dynamic indexes with Liquid that make this much more efficient.

Here is how I generate the Recent Articles section on the homepage:

{% for post in site.posts limit:5 %}
- [{{ post.title }}]({{ post.url }})
  <div class="publishedDateList"> {{ post.date | date: '%B %d, %Y' }}</div>
{% endfor %}

Here is how I generate the Notes index section:

{% assign sorted_pages = site.pages | sort:"path" %}
{% for node in sorted_pages %}
  {% if node.categories contains 'notes' %}
- [{{ node.title }}]({{ node.url }})
  {% endif %}
{% endfor %}

Note the if statement that checks for the presence of the notes category. I want to include a page in notes I simply include category: [notes] in its Front Matter.

Additionally, sorting alphabetically was an issue at one point, so the first line allows for sorting via file name.

With extremely complex Jekyll websites this might add overly to the build time. In which case, importing a static list of links using an include is the solution.

Preventing Liquid Processing Things it Shouldn’t

I had some funny problems on some of my notes because they use syntax (Jinja) that looks like Liquid. raw blocks can be used to tell Liquid to ignore everything inside.

Raw blocks start with raw and end with endraw; each encapsulated within Liquid percent and curly bracket syntax.

Note: it also seems to trigger whitespace to be preserved (like a <pre> block in HTML).

Minimising Layouts

Originally, I had four layouts of almost the same HTML/CSS with some very minor variations, which was rather irritating as it adds to the maintenance burden and I have to document or re-learn what’s different.

I managed to drop down to one single layout by adding some Liquid if statements to the HTML and adding some custom variables to pages/posts that should be rendered differently.

For example:

{% if page.h1 and page.h1 != blank %}
  {{ page.h1 }}
{% else %}
	{{ page.title }}
{% endif %}

On pages where I want the custom variable h1 to override the page title, I simply add h1 to Front Matter:

---
h1: "SOMETHING"
---

Permalinks can either be per page or dynamically generated using some syntax added to _config.yml. (Remember to reload Jekyll!)

The Jekyll documentation covers this one well.

Page TOC

Like the page navigation on the left-hand size of the page?

It’s generated with an extension called jekyll-toc using just Liquid.

You can find it here: https://github.com/allejo/jekyll-toc.

It’s extremely simple to implement and their web page is very self-explanatory.