PBS 178 of X: Getting Started with Jekyll Pages (GitHub Pages)
TO DO
Matching Podcast Episode
You can also Download the MP3
Read an unedited, auto-generated transcript with chapter marks: PBS_2025_03_17
Instalment Resources
- The instalment ZIP file — pbs178.zip
Mini-Series House Keeping
The Demo Site for Remaining Examples
In the remainder of this series, we’ll be learning more about Jekyll as a Content Management System (CMS) by building a demo site that contains all the features we’ll be discussing. It’s going to be one site that we effectively started in the previous instalment, and each instalment will use the final state of the site at the end of the previous instalment as the starting point for all examples.
This instalment’s starting point is the demo site’s starting point, and while it is similar to the final example in the previous instalment, it is not identical. The initial content has been updated a little to make it more appropriate, and it does not include the silly blue background or pointless Bootstrap alert added in the final example — those were intentionally garish to make it very obvious that we could alter Bootstrap’s defaults and that Bootstrap’s JavaScript had been successfully imported.
To make this as painless as possible, and to facilitate people dipping in and out at any point in the series, the site will be versioned on GitHub with tagged releases marking each instalment’s starting point. As a reminder, in Git, a tag is a human-friendly label for a specific commit. It effectively becomes a name for a snapshot of the code at a specific point in time (for more on tagging see Instalment 108).
This approach gives listeners three options for playing along for the remainder of this mini-series:
- Download or fork the original repo once to start this instalment’s examples, and keep that same copy current all the way through by completing each instalment in turn and testing the code locally. This approach requires you to complete each instalment perfectly all the way through the series, so is the simplest but the most brittle.
- Start fresh each time by downloading the compressed version of the full codebase from the appropriately named release and then playing along with that instalment’s examples and testing locally. This approach is more laborious but less brittle.
- Fork the repo on GitHub, pull the appropriate changes from the original (
upstream
) repo as needed, and test both locally and on GitHub Pages. This approach involves the most Git proficiency, but allows for the fullest possible experience.
You’ll find the repository on GitHub as bartificer/pbs-jekyll-demoSite.
File Path Conventions
Up to this point in the entire PBS series, it’s always been obvious what base folder relative file paths referred to. For example, when working with basic HTML and CSS websites, paths were relative to the folder containing the example site. When working with client-side web apps, paths were relative to the folder containing the demo app. And when working with Git, paths were generally relative to the repository’s containing folder.
Up to this point in our exploration of GitHub Pages, paths have been relative to the repository as a whole. This made sense because we were learning how to serve web apps of different kinds using different processes and requiring different folder structures within the repository. Our focus was very much on using GitHub Pages to do some specific tasks.
Now that we know how to use GitHub Pages to host a Jekyll-based site, our focus is shifting from using GitHub pages with Jekyll to using Jekyll as a Content Management System — where Jekyll is run is not the point anymore, it’s about using Jekyll.
This shift in focus from Jekyll on GitHub Pages to Jekyll as a CMS means it no longer makes sense to use the Git repository as the base for relative paths. Instead, we’ll be using the base of the Jekyll site itself as our base, so while we will be storing our site in the docs
folder of our repository and hosting that repository on GitHub, we won’t be referring to the home page as docs/index.md
but just as index.md
.
Cloning an Existing Site on a New Device
Before you begin, check you have the needed Ruby environment in place on the new device by opening a fresh terminal and verifying the following:
- That you are running the correct version of Ruby with the command
ruby --version
, that should match the version described in instalment 177 (As of March 2025 that isruby 3.3.4
). - That you have Bundler installed by running the command
bundle --version
If your environment is not prepared, follow the instructions in the section ‘Preparing to Run Jekyll Locally’ in instalment 177.
Once your environment is ready, clone the existing site to your device and open a terminal in that folder. Change into the folder that contains the site (probably docs
if the site is intended for publication on GitHub Pages, so probably cd docs
).
At the very least our current directory should now have a Gemfile
, and it should probably also have an _config.yml
, and an index.md
.
All you need to do to get up and running now is to instruction Bundler to configure Ruby in your newly cloned folder based on the content of the Gemfile
with the command:
bundle install
That’s it! You should now be able to run your site locally with the usual bundle exec jekyll serve
command.
Worked Example 1 — Set Up the Starting Point for this Instalment’s Examples
Get a copy of this instalment’s starting point, the tagged release pbs178-startingPoint, by either:
- Downloading the zipped or Gzipped version of the full codebase at the correct point in time from the link above and extracting the contents.
- Forking the repository on GitHub and then:
- Cloning your fork to your computer with your favourite Git client
- Checking out the commit that is tagged as
pbs178-startingPoint
(as is always the case when checking out a specific commit rather than a branch, this will result in your local repository having a detached head) - Creating a new local branch at the newly checked-out commit so you no longer have a detached head and can commit your work
Open your copy of the repo in your terminal, and then:
- Change into the docs folder with the command
cd docs
- Initialise the Ruby setup with the command
bundle install
- Deploy the site locally with the command
bundle exec jekyll serve
Understanding Jekyll’s Build Process
Fundamentally, Jekyll takes a folder of input files, processes them in some way, and creates a new folder with output files that comprise the assembled website. Jekyll’s documentation refers to a single execution of this conversion process as a build session. When running a build session on a local machine, the entire contents of our docs
folder are the input, and the generated website is rendered to the special folder docs/_site
. When running a build session in the cloud, GitHub Pages takes the latest version of the docs
folder on the main
branch as the input and writes the output to the GitHub Pages Content Delivery Network (CDN) to make it available on the internet.
A Jekyll build session has four distinct phases that happen sequentially:
- Initialise all plugins, both the standard ones and those specified in the
Gemfile
as configured in_config.yml
. - Read the files in the input folder and populate an internal data structure with that folder’s content.
- Run the appropriate generators to transform the loaded data. This step is further divided into a more detailed sequence of phases:
- A first shallow pass at processing Liquid template tags in all input files. This means that if processing a template triggers the inclusion of another file, those inclusions are not performed yet. They are applied in the final phase of this stage.
- Conversions are applied, rendering Markdown to HTML, Sass to CSS, etc.. The conversion that gets applied is determined by the input file’s file extension.
- A recursive pass is made through all generated HTML snippets that now exist (whether they started as HTML or were converted from Markdown) to assemble them into the final page by applying the appropriate layout to each snippet as defined by the theme. The documentation describes this process like assembling Russian nesting dolls because layouts can include other layouts can include other layouts can include other layouts …
- Render the updated data structure to the output folder using the theme’s templates.
As each input file makes its way through this process, one of three things can happen to it:
- Files can be excluded from the output in one of two ways:
- Ignored — these files don’t appear in the output in any form. The existing content of the
_site
folder is completely ignored (and indeed destroyed!) - Absorbed — these files don’t appear directly in the output, but they do affect it in some way. Files like this can provide content or affect the look and feel of the generated site. The most clear-cut examples of such files are
_config.yml
and the entire_layouts
and_sass
folders.
- Ignored — these files don’t appear in the output in any form. The existing content of the
- Files can be copied unchanged from the input folder into the generated website. The canonical examples here are images. Jekyll doesn’t change them, it just copies them across.
- Files can be processed to transform them in some way as Jekyll copies them from the input folder to the generated website. The canonical examples here are Markdown and Sass files.
Jekyll has clearly defined rules for what happens to each file, and those rules get applied based on three factors:
- The file’s extension
- Whether or not the file contains YAML front matter (empty front matter counts as front matter)
- Any explicit overrides defined in the site’s config file (
_config.yml
)
The following is a good summary of those rules:
-
Unless explicitly overridden in the configuration, files and folders starting with an underscore (
_
) are excluded from the output. Unless those folders have a special meaning, they are ignored. If they have a special meaning, then their content fulfills that role. For example, using the default configuration, a file named_waffles.txt
will get ignored, but all HTML files in the folder_layouts
are interpreted theme layouts. - Markdown files are converted to HTML. By default, all the following file extensions get treated as Markdown files:
.markdown
,.mkdown
,.mkdn
,.mkd
&.md
. - All other text files that contain front matter are processed in some way:
- Files with any of the following file extensions get treated as assets and are processed in a special way:
- Files with either
.sass
or.scss
file extensions are assumed to be Sass file and get converted to CSS using the standard jekyll-sass-converter plugin. - Optionally, files with the
.coffee
file extension can be treated as CoffeeScript and get automatically converted to JavaScript using the optional jekyll-coffeescript plugin. We will not be using Coffee Script in this series.
- Files with either
- Liquid template tags are rendered (in all text files that contain front matter, including Sass & CoffeeScript files). As a reminder, liquid tags are templating tags similar to Mustache which we used extensively for our client-side web apps earlier in the series (particularly instalments 73 & 74).
- Files with any of the following file extensions get treated as assets and are processed in a special way:
- All other files are simply copied to the output — these files are referred to as static files in the documentation, and
site.static_files
in Jekyll’s internal data structure.
Armed with this understanding of Jekyll’s rendering process, we’re now ready to start adding some content to our example site.
Adding Static Files and Assets
In the Jekyll world, the word asset can cause confusion because it’s used in multiple sections of the documentation to describe two different but somewhat related concepts — one Jekyll-specific, and one generic. Just to add to the confusion, there is also a very common convention used throughout the Jekyll community and referenced in the documentation to store files that fall under either definition of the word asset in a folder named /assets
.
The generic meaning of an asset when building web sites is that assets are files that don’t contain content, but do provide some other functionality to the site. This definitely includes CSS style sheets, JavaScript code files, fonts, icons, and UI and theme graphics. But, there are also grey areas where there is no universal agreement, and different web frameworks will encourage different conventions, and of course different developers will have differing and conflicting strongly held opinions. The greyest of grey areas are image files that are not part of the theme, but part of the content.
In Jekyll jargon the meaning of the word asset is much narrower, meaning just two very specific things:
- Sass files with front matter that get converted to CSS files when the site builds (enabled by default)
- CoffeeScript files with front matter that get converted to JavaScript files when the site builds (disabled by default)
There is a strong convention that’s widely used when it comes to where Sass, CSS and JavaScript files should be organised — use subfolders in /assets
. We’ve already been using that convention, and we’ll continue to do so for the remainder of the series.
There is no similarly strong convention for image files, but I believe that it makes the most sense to divide images into two groups, depending on whether they are part of the site’s look and feel, or part of the site’s content.
When you think of the kinds of images that form part of a site’s look and feel you may end up with logos used in page headers or footers, icons used in menus or other standard components, and maybe background images. ‘Graphics’ is good word to describe those kinds of images, so we will be storing such images in /assets/graphics
.
Images that form part of the content could be just about anything. You might need to add some logos to a review, or some photos to a post describing a recent hike, or some graphs and charts to illustrate a complex argument. Generically though, all those images serve to illustrate a point, so we will refer to them as ‘illustrations’. Because they belong to the content rather than the theme, they are different from the style sheets, scripts, and graphics, so they deserve their own top-level folder adjacent to assets
. We’ll be storing our illustrations in /illustrations
.
Putting it all together, we’ll use the following structure:
Asset/Static File Type | Location | Description |
---|---|---|
Style Sheets (*.scss ) |
/assets/css |
Style sheets, written in Sass. |
Client-Side Scripts (*.js ) |
/assets/js |
Scripts that execute in the browser, written in JavaScript. |
Looks & Feel Related Graphics | /assets/graphics |
Images of any kind used to style the site. |
Illustrations | /illustrations |
Image of any kind that are part of the site’s content. |
Worked Example 2a — Add an Illustration to the Home Page
To add an illustration to the home page we’ll first need to add an illustration into our site. Inside the site’s home folder (docs
) create a new folder named illustrations
, and copy the file illustrations-siteIllustration.png
from the instalment resources ZIP into this folder and rename it to siteIllustration.png
.
This image is now a static file, so if you run Jekyll locally you’ll see it gets copied unchanged from the site’s input folder (docs
) to the generated site docs/_site
, this means we can access it from our home page (index.md
) by its relative file path illustrations/siteIllustration.png
. Let’s update the home page to replace the placeholder text that’s there at the moment with something a little more useful, and add our illustration using the standard Markdown syntax for an image with alternative text.
You’ll find the complete new content of the home page in the instalment ZIP as index-1.md
, copy that into your site’s home folder (docs
) as index.md
. The important part to note is the Markdown image syntax:

Rebuilding your site locally will show the updated text and the new illustration.
Notice that the image is not being displayed well by our current very basic theme. The correct fix for this would be to update our theme to add the needed styles for displaying images nicely. However, we’re not ready to do that just yet, so we’ll apply a quick fix to just this image. We’ll use our need for a quick fix as an opportunity to learn a little more about how Jekyll converts Markdown files to HTML.
Jekyll’s Markdown Converter — Kramdown
As is typical for open source projects, the Jekyll team did not reinvent the wheel by building their own custom Markdown converter. Instead, they leveraged an existing open source converter, Kramdown.
For the most part, Kramdown is a very standards-compliant Markdown converter, so if you stick to the original Markdown specification, you’ll never need to think about Kramdown at all.
But when you need to go beyond the standard parts of Markdown, the details matter, because while there are a lot of commonly adopted pseudo-standard extensions to Markdown, each converter picks and chooses which to include, and exactly how to do so.
So, when you need advanced Markdown syntax in Jekyll, consult the Kramdown syntax documentation!
Worked Example 2b — Make the Home Page Illustration Fluid
The problem we need to solve now is displaying our illustration so it scales to the size of our browser window. Our basic theme incorporates Bootstrap, so what we need is the ability to add a CSS class to our illustration, specifically, to add the Bootstrap class .img-fluid
.
We’ll add a style
attribute to the generated <img>
tag. Kramdown supports this via its Inline Attribute Lists syntax. For our specific problem we need to add a class, so the syntax we need is to post-fix {:.CLASSNAME}
. Specifically, we need to add {:.img-fluid}
to the Markdown for our illustration:
{:.img-fluid}
You can edit index.md
manually to update the line, or, you can copy the updated copy of the entire file at index-2.md
in the instalment ZIP over your current index.md
.
Once you regenerate your site you’ll see that the image now scales dynamically as you resize your window. If you view the source of the generated home page you’ll see that our Markdown has been transformed into the expected HTML:
<img src="/illustrations/siteIllustration.png" alt="An Illustration showing the PBS logo over the GitHub and Jekyll Logos with a plus between them" class="img-fluid" />
Introducing Jekyll’s Simplest Taxonomy — Pages
We’re going to start with the simplest of all Jekyll’s taxonomies, pages.
All HTML and Markdown files not located in special folders are treated as pages. They’re simply referred to as pages in the documentation, and they appear in Jekyll’s internal data structure as site.pages
.
HTML and Markdown files appear in the generated site with the same relative path and file name as they did in the input folder, but always have the .html
file extension. The Jekyll server will serve them without the need for adding any extension at all, so in effect, you should mentally thing of them as getting mapped like so:
index.md
orindex.html
→https://mysite.whatever/
waffles.md
orwaffles.html
→https://mysite.whatever/waffles
When it comes to processing Liquid template tags in pages, the following simple rules apply:
- Liquid tags get processed in all Markdown files
- Liquid tags get processed in HTML files that have front matter (and not in HTML files that don’t)
Worked Example 3 — Add ‘About’ and ‘Useful Links’ Pages
Our site is already using the pages taxonomy. The home page is a page in this taxonomy, albeit a special one. To really illustrate how this taxonomy works, let’s add two additional pages, one describing the site’s purpose, and one listing some useful links.
Both will be simple Markdown files with YAML front matter stored in the site’s root folder. You’ll find copies of both files in the instalment ZIP as about.md
and links.md
. Copy both of these files into your site’s base folder (next to index.md
).
Both files are structured in the same way, so let’s have a closer look at just one of them, about.md
:
---
title: About
---
# About this Site
This site is part of the Programming by Stealth tutorial and podcast series co-created by [Bart Busschots](https://www.bartb.ie/) and [Allison Sheridan](https://www.podfeet.com/). You'll find the series itself at [pbs.bartificer.net](https://pbs.bartificer.net/).
Starting in early 2025, the larger Programming by Stealth series is exploring the use of [GitHub Pages](https://pages.github.com) in general for hosting client-side web apps and statically generated websites in general, and [Jekyll](https://jekyllrb.com)-based sites in particular. This site is being built as a worked example throughout this series-within-a-series.
As you can see, the file’s body consists of a top-level heading and two paragraphs written in Markdown, and the paragraphs contain some standard Markdown links.
At the top of the file is a very short front matter section enclosed between triple-dash lines and written in YAML. In this case the YAML represents a very simple dictionary with a single key title
that has the value About
.
Once these two files have been added to your site, build it locally and you’ll see that Jekyll has converted about.md
to _site/about.html
, and links.md
to _site/links.html
. You can view these sites locally at the URLs http://127.0.0.1:4000/about
& http://127.0.0.1:4000/links
(Note there is no file extension needed in the URL.)
You’ll notice that our basic starter theme has used the title
from the front matter in the window/tab name for the page.
Because we have not yet added any functionality to our custom theme, adding these pages to the site did not update any kind of page listing on the home page. Unless you know the URL for the new pages, you’ll never find them!
For now, we’ll manually add a page listing to the home page, but note that this is absolutely not the correct approach for the long term!
Edit index.md
and add a section linking to the pages:
## Site Pages
* [About](about)
* [Links](links)
You can manually edit the file, or you can replace your current index.md
with index-3.md
from the instalment ZIP.
Notice that the URLs for these links are incredibly simple, they are relative to the home page, and consist of the filename without a file extension.
Rebuild your site locally to see the listing, and click on a link to verify that it works.
Once you navigate to one of the pages notice that there’s no way to get back to the home page, we’ve just highlighted another shortcoming of our basic theme. We won’t address that shortcoming in this instalment or the next, but we will shortly.
Final Thoughts
We’ve only just gotten started, and we’ve already picked up three pieces of technical debt:
- We manually added a Bootstrap class to an image rather than updating our theme to display all images well.
- We manually built a list of pages to our home page when we should really be letting Jekyll do that kind of chore for us.
- We have no way to get from a page back to the home page.
All three of these pain points will serve as useful jumping-off points for further instalments, but we’re going to start with the second — the page listing on the home page.
When we added the Markdown files for our two new pages to our site, we caused Jekyll to update the internal data structure it assembles when building our website. That data structure is accessible to us, and we can use it to automate the generation of our page listing. To do that, we need to learn about two related concepts — the data structure itself, and Jekyll’s templating syntax, Liquid. This will be our focus for the next instalment.