Setting up personal blog in Jekyll

jekyll
 
markdown
 
setup
 

Some notes on issues/solutions I had/used for my Jekyll blog.

Basic Concepts

What’s Jekyll

Jekyll is an open source static site generator. It is the engine for Github/Gitlab Pages. It is widely used as personal blogs as well as organization wikis. It is developed in ruby (.rb) and unittests written in Gherkin (.feature, Cucumber behavior-driven development).

Directory Structure

My initial Jekyll root directory:

(base) rafaeljin@rafaeljin-XPS-13-9350 ~/sandbox/rafaeljin.gitlab.io (master) $ ls -F
404.html  about.md  _config.yml  css/  feed.xml  Gemfile  Gemfile.lock  _includes/  index.md  _layouts/  _posts/  README.md  _sass/ _site/

A few key points:

  • Jekyll supports static page generation for both Markdown files and html files. If both file types exist for the same file (e.g index.html and index.md), the html file is chosen over the Markdown file. For example, if we want to use index.md as our index file, index.html needs to be removed. Jekyll documentation on Order of Interpretation has more details on this;

  • Post files are stored in _posts/, and requires a format of YYYY-MM-DD-topic-name.md. The Jekyll engine matches posts of:

MATCHER = %r!^(.+/)*(\d+-\d+-\d+)-(.*)$!.freeze

These should be enough to get started. Jekyll documentation on directory structure has more details on the purpose of each file and directory.

Configurations and Setup

Deploying on Github/Gitlab

Both Gitlab and Github named the feature Pages (Gitlab Pages/Github Pages), but there are significant differences how the two companies manage/advertise the feature.

  • Github provides Pages as a service, with not much room to customize it. All available options are listed in the below screenshot:
    Github Settings

    Additionally,** Github limits the service to public repos** (‘Pages’ on private repos is only provided to premium users). Github documentation provides details on configuring Jekyll, troubleshooting Jekyll build errors, etc.

  • Compared to Github, Gitlab offers way more flexibility. To begin with, Gitlab provides free services to private repos under the condition that the CI job to build the website uses no more than 3000 CI minutes per month. The default Jekyll CI job (.gitlab-ci.yml in the root directory) takes 3~4 minutes to build the website artifacts, meaning we can upload near a thousand commits each month and have it automatically rebuild on each commit for free. The result of the CI job (website artifacts) can also be downloaded. The screenshot below shows the interface of the Gitlab CI job:
    Gitlab Settings

    From this page, we can see every step/command of how each job is run, and it’s rather easy to debug build failures. For more information, Gitlab documentation offers very detailed explanation on the build process, the architecture of the building engine and in-depth explanation of technologies behind it.

Running Locally

We need to build our website locally sometimes, whether it is for testing features that we don’t plan to publish, or maybe just faster turnaround time than the remote CI job. To build it locally, after installing jekyll using gem, we can execute jekyll serve [--trace] in the jekyll root directory and access the pages on http://localhost:4000. The site artifacts will be generated in _sites directory. Updates to pages will be automatically updated because --watch is set by default for jekyll serve, but updates to configs (i.e. _config.yml) require restarting. Additionally, with --drafts options, we can preview the posts in _drafts locally.

Post-setup

Markdown

Apart from supporting html/css, Jekyll supports some additional formats and Markdown is one of them. Jekyll supports many different Markdown renderers out of the box, and even gives users the power to use custom Markdown processors. Here are a few options:

  • Kramdown is a Markdown renderer written in Ruby, and the default Markdown processor of Jekyll. Looking at its repo, it seems to be one of the more active repos out of the bunch. It also seems to be the preferred renderer of Jekyll user base. (If you haven’t noticed, Kram is Mark spelled backwards!)
  • CommonMark has two features that stands out:
    1. It is developed in C, and its the faster renderer. When the project scales up significantly, it might be an important factor;
    2. It follows CommonMark Spec, an effort of standardizing Markdown syntax.
  • Redcarpet another widely used Markdown renderer developed in Ruby. It offer a wild range of support for syntax not supported in other renderers.
  • Another Markdown renderer worth mentioning is Github Flavored Markdown. It is the Markdown renderer for Github contents.

I’m sticking with default option Kramdown for the time being and in Markdown Cheatsheet I detail a list of Markdown syntax based on Kramdown (as well as some Jekyll specific sytax).

UPDATE: Jekyll 4.0 drops support for other markdown processor, now only supports Kramdown. Jekyll documentation is outdated but Jekyll 4.0.0 Release Notes does mention the drop of support. Confirmed when attempted to use a previously supported Markdown renderer:

Markdown processor: "redcarpet" is not a valid Markdown processor.
    Available processors are: kramdown

Liquid Templating Language

Another format/language supported by default in Jekyll is Liquid Templating Language. In Liquid, there are 3 main concepts:

  • objects are elements to be outputted. Denote content with {{ and }} and it will be rendered;
  • tags is the backbone of Liquid, everything ranging from basic control flows, comments to complex statements are tags. Shopify tags and Jekyll tags. Tags are denoted with {% and %} ;
  • filters are like functions. The input and the ‘function’ are separated with a |.
  • front matter is a Jekyll concept that does initialization for Liquid. The initialization is done between two ---s at the top of the file. Notice that the front matter category actually denotes the complete of the posts and not it’s tags. E.g. if you have
    categories: a b 
    

    your post will be located at your.jekyll.website.com/jekyll/a/b/YYYY/MM/DD/your-post.html.

Themes

One theme that really caught my eyes is hyde from poole:

The Strange Case of Dr. Jekyll and Mr. Hyde tells the story of a lawyer investigating the connection of two persons, Dr. Henry Jekyll and Mr. Edward Hyde. Chief among the novel’s supporting cast is a man by the name of Mr. Poole, Dr. Jekyll’s loyal butler. Poole is the butler for Jekyll, the static site generator. It’s designed and developed by @mdo to provide a clear and concise foundational setup for any Jekyll site. It does so by furnishing a full vanilla Jekyll install with example templates, pages, posts, and styles.

A few notes using hyde:

  1. Header level: aesthetic speaking, starting the top header at the third level worked out the best for me;
  2. For poole, some css are defaulted in public directory, which is seemingly taken in the Gitlab Pages. An easy workaround is to rename poole’s public directory.
  3. Some of the poole/hyde default plugins are not supported in jekyll-4.0, and it might need manual fixes.

Additional Features

  • Comment section. There are various ways to create a comment section - from free to premium, from self-hosted to using providers’ services, etc. Darek Kay’s post summarizes the pros and cons of each approach nicely. I opted to use Disqus as I don’t have any traffic yet, following Desired Persona’s post (don’t forget to mark your domain as trusted domains in Disqus).
  • Website analytics services. Google Analytics provides free service for website owners to track their website traffic. Another post from Desired Persona provides an easy-to-follow setup guide.
  • Encrypted posts. My blog serve much as reference notes as a personal blog to me. There are notes that I prefer to keep private, but in the mean time also accessible from the website. lIlychen provides a solution on github using gulp. A few points on the solution:
    1. some syntax changes occurred between gulp versions 3 and 4. Use npm install gulp@3.9.1 ;
    2. after creating encrypted post in _protected, run gulp to generate a regular post with hashed permalink to the encrypted post;
    3. it is not meant to provide high level of security - it is prone to brute-force attack, use with caution;
    4. gulpfile.js is not compatible with Markdown table syntax that contains ---. Diff 1 shows the changes for it.
    5. another small tweak to add a chunk of non-hidden text to show up in posts excerpts (or just simply just not hidden). In my site, I added an additional --- block for non-hidden text in encrypted posts. Changes to gulpfile.js is shown below in Diff 2:

Diff 1:

-      if (chunks.length === 3) {
+      if (chunks.length >= 3) {
         checkEncryptedLayout(chunks[1], file.path);
         frontMatter = chunks[1];
-        originalBody = chunks[2];
+        originalBody = chunks.slice(2).join('---');
       } else if (chunks.length > 1) {
         this.emit('error', new PluginError({
           plugin: 'Encrypt',

Diff 2:

@@ -62,12 +62,15 @@ function encrypt(password) {
       var delimiter = '---',
           chunks = String(file.contents).split(delimiter),
           originalBody = chunks[0],
-          frontMatter = '';
+          frontMatter = '',
+                 notHidden = '';
 
-      if (chunks.length >= 3) {
+      if (chunks.length >= 4) {
         checkEncryptedLayout(chunks[1], file.path);
         frontMatter = chunks[1];
-        originalBody = chunks.slice(2).join('---');
+               notHidden = chunks[2];
+        originalBody = chunks.slice(3).join('---');
+               originalBody = [notHidden, originalBody].join('\n');
       } else if (chunks.length > 1) {
         this.emit('error', new PluginError({
           plugin: 'Encrypt',
@@ -79,7 +82,7 @@ function encrypt(password) {
       var encryptedBody = cryptojs.AES.encrypt(marked(originalBody), password),
           hmac = cryptojs.HmacSHA256(encryptedBody.toString(), cryptojs.SHA256(password).toString()).toString(),
           encryptedFrontMatter = 'encrypted: ' + hmac + encryptedBody,
-          result = [ delimiter, frontMatter, '\n', encryptedFrontMatter, '\n', delimiter ];
+          result = [ delimiter, frontMatter, '\n', encryptedFrontMatter, '\n', delimiter, '\n', notHidden ];

Q&As

index.html won’t build

Make sure index.md or index.html is in the root directory.

Jekyll can’t find the images

Images needs to be stored in a non _-prefixed directory, or Jekyll won’t be able to find the reference.

Main page redirects to gitlab 404

Make sure repo name matches username.gitlab.io, check Pages settings.

Centralized information on Jekyll

Although a google search usually leads to the answer of your question, there are three hubs of documentation very worth reading: