Cozy Computer

Email logo Twitter logo Github Mark logo <——- Find me (Patrick McCarver) (they/he)

Setting up a personal blog using Jekyll, Github Pages, and a custom domain name 3950 words (16 min)

This is my first post! It seems fitting that my first post should be about how to build a blog like this one.

cozy.computer is built using:

Choosing a static website generator

Notes on static websites

Jekyll is a simple tool for building a static website.

A static website is one that is delivered to your browser exactly as it is stored. When you visit a static web page, you will always receive the same page no matter who you are and no matter where you’ve come from.

A dynamic website is the opposite - the pages you visit are generated by the server.

Static websites are useful because they can be easy to set up and host. You don’t have to configure and run a server (there’s still a server, you just don’t need to set it up), don’t have to deal with modeling your content, don’t have to setup a database, don’t have to figure out caching. Just give the files to your host and have the host serve them when they are requested.

Static websites are the foundational websites of the internet. Many early webpages were just static files in a folder served from a server. As websites became more complex,dynamic websites allowed for serving different users different content, allowed users to create their own content, allowed management of user sessions, storage and retrieval of content from databases and memory stores, and more.

Although they’re old fashioned, static website generators are still hugely popular and are used by many companies including Nest, MailChimp, and Vox Media.

One reason for this is cache invalidation - knowing when cached content needs to be refreshed. Because of the large number of dynamic elements in a modern web page, it can be difficult to determine if its content needs to be updated or not. Caches of web pages are used to save network traffic and delivery time: cached pages can be saved in the browser or delivered via a Content Delivery Network (CDN) - a network of cache servers where the nearest server can quickly deliver you cached content. Static websites have a simpler caching protocol - if the file has been updated, update the cache.

Principles of static website generators

Static website generators typically:

Static website generators build all template content into a set of static resources (HTML, Javascript, CSS, assets like images and video, etc). These static resources can then be hosted and served on the internet.

Choices of static website generators

Jekyll is probably the most popular static website generator. It uses a templating language called Liquid, supports content written in markdown, and supports Sass CSS preprocessing. It ships with a local development server with hot reloading. Jekyll is used in the background when you host static websites on Github Pages. Some complain that Jekyll is slow to build for larger websites.

Hugo bills itself as the “fastest framework”, meaning it can build much faster than Jekyll. It uses a templating engine based on Go. Hugo also supports content written in markdown and has a local development server.

Gatsby is a static website generator that uses modern web technologies like React, GraphQL, and Webpack to generate a Progressive Web App. It allows for developing a more complicated website with modern features.

I’ve chosen Jekyll because of recommendations from friends at the Recurse Center, because I am only interested in writing simple markdown posts, and because I intend to host on Github Pages which is already set up to work with Jekyll.

Hosting a static website

Static websites are just a collection of files and there are many ways to host them. I’ve chosen Github Pages.

Github Pages lets you host one personal website and unlimited project websites directly from a Github repository for free. It has built-in support for Jekyll and simple setup for HTTPS and custom domain names. I’ve chosen it for these reasons and because I already planned to keep my blog on Github for version control.

Some other free options (depending on usage tiers):

Setting up Github pages and using a custom domain name

Github Pages allows you to serve the contents of your Github repository as a website.

By default visitors can access your personal website at your_username.github.io or your_organization_name.github.io and your project websites at your_username.github.io/project_repository_name. You can also set up a custom domain name if you have one or want to purchase one.

Steps to setting up Github Pages for a personal website:

  1. Create a repository for your personal website and name it either your_username.github.io or your_custom_domain_name. I named my repository cozy_computer.
  2. Go to Settings, scroll down to the Github Pages section, and choose a theme to enable Github Pages. This should create an index.md and _config.yml in your repository.
  3. Also check the box for Enforce HTTPS unless you have a reason not to, although it might take 24 hours after setup for this option to become available.
  4. Optionally, if you have a custom domain, enter it in the Custom domain field.

You should now be able to visit your static website at your_username.github.io but not yet at your custom domain.

Notes on A and CNAME records for domains

Entering the custom domain name above will create a CNAME file in your repository. CNAME records - Canonical Name records - are used to provide aliases to domain names. They allow you to say, for instance, that www.cozy.computer and cozy.computer are the same website.

A records map domain names to IP addresses. They’re how you get from a domain (ex cozy.computer) to the actual machine hosting the static website.

In my CNAME file is cozy.computer. This tells visitors to merklebros.github.io (my default personal website) and visitors to www.cozy.computer to look for cozy.computer when using A records to look up the IP address where my website is hosted.

Setting A and CNAME records at the DNS provider

When you purchase a domain name, the A records are set to point to the DNS provider’s (or affiliate’s) IP addresses. Visitors to your domain might be served an advertisement for the DNS provider or a 404 page.

You can update the A records to point to Github’s servers so that Github Pages can serve your static website instead. Most providers will have a guide for how to do this.

Log in to your DNS provider - mine was namecheap.com - and create four A records that point your domain at the Github servers. Check here to see what IP addresses to point to. Currently they are:

185.199.108.153
185.199.109.153
185.199.110.153
185.199.111.153

By default these probably only redirect requests to your apex domain - a custom domain that does not contain a subdomain (ex example.com).

To redirect the www subdomain (ex www.example.com), add a CNAME record at your DNS provider with HOST set to www and with a value of your_username.github.io.. Note the . at the end of the domain.

When the changes have propagated (maybe up to 24 hours but typically under 1 hour) you can visit your static website at your custom domain.

Setting up Jekyll for local development

To develop your static website locally using Jekyll first install the following dependencies:

Then install Jekyll and bundler gems:

gem install jekyll bundler

Clone your repository from Github and cd into it. Remove any files except for the CNAME file. To set up a new Jekyll project, run:

jekyll new . --force

where jekyll new sets up a new Jekyll project, . in the current directory, --force even if the path already exists (and the current directory does already exist). Start a development server with hot reload activated with bundle exec jekyll serve -l then open http://localhost:4000.

Building the website and directory structure

The default files created by jekyll new are:

Jekyll project directory
- _config.yml
- Gemfile
- Gemfile.lock
- .gitignore
- index.markdown
- about.markdown
- 404.html
- _posts/
- _site/

_config.yml

_config.yml is a YAML (recursive acronym for YAML A'int Markdown Lanaguage) file for configuring Jekyll. You can fill in the details for your website’s title, email, description (the text that will show up in a Google search), url. You can specify any key: value pair here and you will be able to access it in the other files for your website.

My slightly modified _config.yml is below, I’ve removed theme: minima because I like the Github Pages default theme, Primer. I’ve added a custom key: value pair, average_reading_speed_words_per_minute: 250 so that I can show a read time on my posts later.

title: Cozy Computer
author: Patrick McCarver
email: patrick@cozy.computer
average_reading_speed_words_per_minute: 250
description: >-
  Be cozy and learn things with me (Patrick McCarver)
baseurl: ""
url: "https://cozy.computer"
twitter_username: cozy_computer
github_username: Merklebros
permalink: /:title
plugins:
  - jekyll-feed

Jekyll allows you to specify the path for your posts and pages with the permalink key. This is how they will show up in the address bar of your web page. The default path is long permalink: /:categories/:year/:month/:day/:title:output_ext. I prefer to just show the title so set permalink: /:title, so all pages are available as https://cozy.computer/post_title.

Jekyll has many plugins. The default plugin jekll-feed will create an Atom RSS feed at /feed.xml.

Gemfile and Gemfile.lock

A Gemfile is a file describing dependencies for Ruby programs. These are used with Bundler, a dependency management program for Ruby projects that uses the Gemfile to install Ruby gems and pin them to specific versions with Gemfile.lock. Gemfile is analogous to Python requirements.txt or NodeJS package.json and package-lock.json.

When hosting on Github Pages, you can run into dependency issues because Github Pages rebuilds your website using their version of Jekyll before deploying it. This can lead to unexpected behavior. For instance, different versions of Ruby parse YAML files differently. To ensure that the deployed website behaves the same as the local development build, use the github-pages gem which sets up local dependencies that match Github Pages as closely as possible.

My Gemfile is below. I’ve cleaned up some comments and followed the instructions for setting up the github-pages gem by removing the jekyll gem and uncommenting gem "github-pages", group: :jekyll_plugins. I’ve also removed the minima gem since I’m not using that theme.

source "https://rubygems.org"
gem "github-pages", group: :jekyll_plugins
group :jekyll_plugins do
  gem "jekyll-feed", "~> 0.12"
end

# Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem
# and associated library.
install_if -> { RUBY_PLATFORM =~ %r!mingw|mswin|java! } do
  gem "tzinfo", "~> 1.2"
  gem "tzinfo-data"
end

# Performance-booster for watching directories on Windows
gem "wdm", "~> 0.1.1", :install_if => Gem.win_platform?

Run bundle install to install the dependencies specific to the github-pages gem.

.gitignore

A .gitignore is generated by jekyll new. It tells Git to ignore files related to the Jekyll build process: _site, .sass-cache, .jekyll-cache, .jekyll-metadata, vendor.

Front Matter and index.md

Jekyll generates two .markdown files that can be safely renamed to .md instead.

Markdown files are the content for your website. You can write post and page content using Markdown and then include them into HTML layouts.

At the top of every page you can optionally include YAML key-value pairs to use in the document. Jekyll calls this Front Matter.

In index.md, the layout is set to home but the layout file is hidden from view by the default theme so we can’t see what home.html looks like. This is strangely by design to ease the user experience. Remove layout: home and type a little poem along with a list of projects and blog posts for the home page. Here’s my index.md:

Cozy computer is CC to me
Spindled in comfort and beeping softly
Cozy computer with cuppa coffee
Or mocha, or Hi-C, or even Capri

Cozy computer (CC) whispers squeaky
Look upon my works ye mighty, and drink tea

## My works
[a 404 work](work.md)

## Blog
{% for post in site.posts %}
[{{post.title}}]({{post.url}})
{% endfor %}

We can see that in Markdown you can link to other Markdown files, and you can use the Liquid templating language to insert content. The last three lines take every post (accessible under site.posts) and make a link for each post.

Layouts and includes

Jekyll supports custom layouts and looks for them in the _layouts folder. Create this folder and create default.html there. Jekyll will use a default layout for index.md without having to specify it in the Front Matter. I’m using a slightly modified default.html from the Primer theme available from their Github repo.

<!DOCTYPE html>
<html lang="{{ site.lang | default: "en-US" }}">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

{% seo %}
    <link
      rel="stylesheet"
      href="{{ "/assets/css/style.css?v="
        | append: site.github.build_revision
        | relative_url }}"
    >
  </head>
  <body>
    <div class="container-lg px-3 my-5 markdown-body">

      <h2><a href="{{ "/" | absolute_url }}">{{ site.title }}</a></h2>

      {% include contact_navigation.html %}

      {{ content }}

      {% include footer.html %}

    </div>
    <script
      src="https://cdnjs.cloudflare.com/ajax/libs/anchor-js/4.1.0/anchor.min.js"
      integrity="sha256-lZaRhKri35AyJSypXXs4o6OPFTbTmUoltBbDCbdzegg="
      crossorigin="anonymous">
    </script>

    <script>anchors.add();</script>

    {% if site.google_analytics %}
    <script>
      (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
      (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
      m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
      })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
      ga('create', '{{ site.google_analytics }}', 'auto');
      ga('send', 'pageview');
    </script>
    {% endif %}
  </body>
</html>

The default.html is a template for every page. Place the basic structures of your website that will be present on all pages here. Liquid templates are either {% do stuff %} or {{ variable }} depending on whether they have logic in them or just reference a variable. The {% seo %} adds some metadata to the page. In the body I dynamically include contact_navigation.html - a social media navigation bar that I want on every page, content which is the contents of whatever markdown file is given to this template, and footer.html, the site footer.

The include keyword is used to add contact_navigation.html and footer.html because they are partials - small snippets of HTML you can compose in other templates. Jekyll looks in the _include folder for partials you include. Create the _include folder and inside create contact_navigation.html:

<p id="contact-navigation">
  <a href="mailto:{{site.email}}">
    <img src="assets/icons8-email-32.png" alt="Email logo">
  </a>
  <a href="https://twitter.com/{{site.twitter_username}}">
    <img src="assets/twitter_logo_blue_small.png" alt="Twitter logo">
  </a>
  <a href="https://github.com/{{site.github_username}}">
    <img src="assets/github_mark_small.png" alt="Github Mark logo">
  </a>
  &lt;——- Find me (Patrick McCarver)
</p>

The contact navigation bar uses images that I’ve saved in /assets but you can save them wherever you want. You can access site variables from _config.yml using site.variable_name, ex site.email, site.twitter_username, etc.

Inside _include/footer.html:

<div class="footer mt-5 pt-3 text-left text-gray">
  <p>© {{site.author}}</p>
  <p>
    <img src="assets/recurse_logo_small.png" alt="Recurse Center Logo">
    <---- Want to become a better programmer?
    <a id="recurse-center-link" href="https://www.recurse.com">
      Join the Recurse Center!
    </a>
  </p>
  <p>Email icon by
    <a href="https://icons8.com" alt="https://icons8.com">Icons8</a>
  </p>
</div>

The scaffold for the home page is finished! Let’s create one for posts in _layouts/post.html.

---
layout: default
---
<h1>{{page.title}}
  {% assign word_count = content | number_of_words %}
  <span id="post-word-count">
    {{word_count}} words
    ({{
      word_count |
      divided_by: site.average_reading_speed_words_per_minute |
      plus: 1 |
      round
    }} min)
  <span>
</h1>
{{ content }}

I want posts to look the same as the home page but with different content, so I use the default layout. I’ll display the page’s title, word count, read time, and content. Liquid template language uses | to pass variables to Liquid internal functions, like in Bash. In {% assign word_count = content | number_of_words %}, content is passed to number_of_words, an internal liquid function for calculating number of words. Below that I do my own math to display the reading time. Like before, content is the content of the Markdown file we pass to this template.

404.html

There’s a 404 page you can customize but I just leave it alone.

Posts

Jekyll looks for posts in _posts and drafts in _drafts. Posts are markdown files that must be named with the date leading: YYYY-MM-DD-title-of-post.md.

You can include Front Matter YAML at the beginning of the post to use in your templates. Jekyll supports assigning categories and tags to pages and posts. Categories are meant to be separate sections of the website and get their own url string with the category name in it: your_domain.xyz/category/blah. Tags are used internally so that you can find all pages or posts with that tag. Below is the Front Matter for this post:

---
layout: post
title:  "Setting up a personal blog using Jekyll, Github Pages, and a custom domain name"
date:   2020-02-17
---

The rest of the post goes below the Front Matter and will be inserted as content into the post template.

Deploying

Jekyll builds your static website locally in _site/. Depending on your deployment you may or may not need the built website.

As I’m hosting with Github Pages, I’m free to push my repository as-is and Github Pages will build it for me.

That’s it! To continue building the website just update pages or posts and push the changes to Github and the site will be continually redeployed.

A fun note: While writing this post Liquid kept trying to interpret my quoted code blocks with {{}} and {% %} in them as Liquid templates instead of plain text. To get around this, you can wrap the entire text in raw tags as discused here.