PBS 177 of X: Publishing A Basic Jekyll Site (GitHub Pages)
In the previous instalment we learned how to use GitHub Actions to publish a web app using a custom build system, Webpack to be precise. We’re now moving on to solving a completely different problem — using GitHub Pages with Jekyll as a content management system. This is GitHub Pages’ default mode of operation, so we won’t need to build our own custom GitHub Actions this time, instead, we’ll be relying on the default GitHub Pages workflow provided by GitHub.
In the previous instalment, we made a point of using GitHub Pages in such a way that we could test our apps locally before pushing the tested code to GitHub and triggering an automated deployment to the internet. This kind of local testing removed a lot of friction from the development process, greatly speeding up debugging in particular. Hence, we’ll focus on using GitHub Pages and Jekyll in such a way as to facilitate the same kind of local testing. This approach complicates the initial setup of our projects a little, but that extra work up front more than pays for itself over the lifetime of any site developed in this way!
Finally, rather than building the theme for our sites completely from scratch, we’ll use Bootstrap as our scaffold, and build on top of the solid foundation it provides. Because Jekyll supports Sass (Syntactically Awesome Style Sheets) out of the box, and because Bootstrap uses Sass for customisation, this is the perfect opportunity to learn how to customise Bootstrap. Bootstrap was built to be both configurable and extendable, so its default behaviour is just a taste of its full capabilities.
In this instalment we’re going to start by laying a solid foundation for our work with Jekyll. We’ll learn how to start a Jekyll project on our local PCs, test and run it, and then publish it to the internet with GitHub Pages. We’ll start our Jekyll project with the simplest possible HTML 5 theme, and then add Bootstrap in such a way that it is ready to be customised later in the series.
Matching Podcast Episode
You can also Download the MP3
Read an unedited, auto-generated transcript with chapter marks: PBS_2025_03_01
Instalment Resources
- The instalment ZIP file — pbs177.zip
Some Important Context
It’s important to note that if you don’t want the ability to test your GitHub Pages site locally, you can skip much of this instalment and simply enable GitHub Pages to start serving your docs
folder just like we did in the first of the two worked examples in the previous instalment. At that point you can create a standard Jekyll directory structure and just start building your site.
However, there’s a very good reason not to take that easy route if you plan to use Jekyll as a full content management system — if you don’t follow these steps, your only means of testing your site will be to actually publish it to the internet by doing a full commit to main
for each and every change you want to test. This is inordinately tedious for debugging work!
The extra work described in this instalment results in a Jekyll site that is both compatible with GitHub Pages, and with a local install of Jekyll.
I mentioned in the introductory instalment that GitHub Pages uses a customised version of Jekyll, and that’s a vitally important point to understand. Because if it’s various customisations, you don’t install Jekyll directly in your docs
folder, instead you install a github-pages
package which lists many dependencies, including the specific version of Jekyll currently supported by GitHub Pages. This is always a somewhat older Jekyll version. As of March 2025, the main Jekyll project is at version 4.3
, but GitHub Pages is using Jekyll version 3.10
.
This lagging version number is important to keep in mind when reading the Jekyll documentation. Newer features will be annotated with the Jekyll version in which they were introduced, so at any time, some of the newer features described in the documentation will not yet be available for use with GitHub Pages.
Preparing to Run Jekyll Locally
Note: the instructions in this section need to be run on any machine where you have not used Jekyll before, even if you are cloning an existing Jekyll project from Git rather than starting a fresh one.
To test a Jekyll site locally you need to install Jekyll on your computer. Jekyll is written in Ruby, so the first thing you’ll need to do is install Ruby 3.
Warning: before installing Ruby 3 make sure you have the latest version of the Xcode developer tools installed and that the license has been accepted. You don’t need the full Xcode, you just need the Command Line Utilities. Look for the latest non-beta version available on this page developer.apple.com/…. If your tools are not up to date and the license accepted, strange errors can occur when installing various dependencies later in the process. If the developer tools are not fully in order at the point in time you install Ruby 3, simply fixing the developer tools later will not resolve the issues installing the dependencies, at that point, the only solution will be to completely remove Ruby 3 and start over. The command
xcode-select --install
should trigger their installation or update.
Macs ship with Ruby version 2 installed, but it’s easy to upgrade to Ruby 3 using Homebrew.
To stop random things breaking on your Mac, it’s important not to replace the built-in Ruby, but to augment it with our desired newer version and a tool for flipping your terminal between versions as needed. We’ll be following Jekyll’s advice and using chruby
as our Ruby version manager.
If you don’t already have it installed, start by installing Homebrew using the instructions on their home page.
With Homebrew installed we can now install the universal Ruby version installer and the Chruby version switcher with the command:
brew install chruby ruby-install
Next, install the version of Ruby currently listed on the GitHub Pages dependencies page. As of March 2025 the command to run is:
ruby-install ruby 3.3.4
This will trigger a Unix/Linux style installation from source code, so you’ll see lots of output scrolling by, and it’ll take some time.
When Ruby finishes installing, the next step is to configure your shell to automatically use Chruby with the following commands (replacing the version number with the one you just installed):
echo "source $(brew --prefix)/opt/chruby/share/chruby/chruby.sh" >> ~/.zshrc
echo "source $(brew --prefix)/opt/chruby/share/chruby/auto.sh" >> ~/.zshrc
echo "chruby ruby-3.3.4" >> ~/.zshrc # run 'chruby' to see actual version
If you suspect you may have installed Ruby before, it is important to check that your
~/.zshrc
file does not container older duplicates of the three directives we just added. You can view the contents of the file with the commandcat ~/.zshrc
. If there are older versions, edit the file with your favourite text editor and keep the copies nearest the end of the file, those are the ones we just appended by the above commands.
These instructions assume a modern version of macOS using Zsh, if you’re still using Bash edit the commands to append to
~/.bash_profile
instead of~/.zshrc
!
With all that done, close and reopen your Terminal to pick up the new Ruby configuration. Verify you’re using Ruby 3 now with the command:
ruby --version
We’re going to be installing Ruby’s equivalent of JavaScript packages, but I just want to note that Ruby doesn’t call them ‘packages’, it calls them ‘gems’, which is some fun wordplay on the language’s name 🙂
To easily install Jekyll and its dependencies, we need to install the optional Ruby package manager Bundler. We can do that with the command:
gem install bundler
Now that we have bundler installed, we have everything we need to start running Jekyll locally.
Worked Example — A Basic Jekyll Site with Customisable Bootstrap
We’re now ready to build our first site, and we’re going to do it in such a way that we can test it locally.
Step 1 — Create a New Repository on GitHub
Log in to GitHub and create a new repository. Be sure to choose the following settings:
- Select public from the visibility radio group (GitHub Pages can be used on private repositories, but not for free).
- Not needed, but I strongly recommend ticking the box to Initialise the repo with a README file so you have a place to add instructions to yourself related to the site. This will also initialise the repository, making it easier to start using.
- Choose Jekyll in the Add .gitignore dropdown.
Step 2 — Clone Your Repo & Configure GitHub Pages Locally
Clone your newly created repo and open a command prompt in that folder.
Because Jekyll is a Ruby app, we are going to manage it using Ruby’s equivalent of an NPM configuration file, a file named Gemfile
. This file will declare the specific versions we need for each Ruby package that needs to be installed.
Start by creating a docs
folder, then change into it with the commands:
mkdir docs; cd docs
Rather than managing our Gemfile directly, we’re going to let the Bundler do that for us by using the bundle
command.
Before we can start adding our desired gems we need we need to initialise a blank Gemfile with the command:
bundle init
You’ll see that running this command in the docs
folder created the file docs/Gemfile
.
Older versions of Ruby contained the package webrick
as a standard package, but Ruby 3 doesn’t, so to avoid errors, let’s first add it to our Gemfile with the command:
bundle add webrick
This command both adds the gem to our Gemfile, and, if needed, installs it on our system.
Next, we want to add the GitHub Pages gem. This will install the needed version of Jekyll as a dependency. We can do this with the command:
bundle add github-pages --group jekyll_plugins
You’ll see that this command adds many dependencies. If you scroll up you can see the exact version of Jekyll that was added, in February 2025 the line is:
Fetching jekyll 3.10.0
Step 3 — Create an Initial Skeleton Site
Before we can test everything is working, we need to create a very basic Jekyll site in the docs
folder.
First, we’ll create a very basic settings file for the site by creating a file named docs/_config.yml
with the following contents (you’ll find a copy of this file in the Instalment Resources as _config-1.yml
which you’ll need to rename to _config.yml
):
# Site Settings
title: PBS 177 Test Site
Finally, create a very basic home page by creating a file named docs/index.md
with the following content (included in the resources as index-1.md
which you would rename to index.md
):
---
title: Home
---
# Hello World!
This is a really basic Jekyll site 🙂
Step 4 — Test Jekyll Locally
We now have Jekyll configured and a basic site in place so we’re ready to test it with the command:
bundle exec jekyll serve
This will render the contents of the docs
directory as a website and write the generated files to the folder docs/_site
and then start a locally running web server on port 4000
with that folder as the source. You’ll see the URL for this local server in the output, and by default it will be http://127.0.0.1:4000
.
Note that this command does not exit, but keeps running. You can kill the local server at any time with
ctrl
+c
or closing the common prompt, but you’ll only need to do that when you’re finished for the day, because the Jekyll server monitors the file system for changes, and automatically rebuilds your site each time you change a file in the thedocs
folder. This makes debugging very easy.
To see the generated site, open the URL in your browser.
Notice that the site title we defined in _config.yml
appears in the two places in the default theme:
- In the browser window/tab title
- In the automatically generated navigation at the top of the page as the link to always take you to the site’s home page
Notice that the page title defined in the front matter in index.md
also appears in the browser window/tab title.
The rest of the content of the home page is the rendered markdown from the body of index.md
.
Step 5 — Commit & Push to GitHub
Before we commit our changes, we need to add docs/Gemfile.lock
to the .gitignore
file. You can do that by editing the .gitignore
file manually, by using your Git GUI app of choice, or by running the following command:
echo '/docs/Gemfile.lock' >> ../.gitignore
Note that if you forgot to tell GitHub to use the standard ignore file for Jekyll you’ll also need to add the docs/_site
folder to your ignore file.
You can now commit all remaining new and changed files, and push your commit to GitHub.
Step 6 — Enable GitHub Pages & Test
Log in to the GitHub portal, open your repository, and then navigate to the GitHub Pages settings page (Settings → Pages).
Enable GitHub Pages with the following settings:
- Leave the Source as Deploy from a branch
- Set the Branch to
main
with the folder/docs
and save
Navigate to Actions from the top menu bar and watch the site build.
When the build completes, click on the workflow name to see the details, this will include the published URL of your site.
Open the site in your browser to verify it works!
Step 7 — Add Bootstrap
The version of Bootstrap we’ve used to date has been the compiled version, that is to say, a collection of finished CSS files. If you open those files you’ll find they’re massively repetitive, with very similar code repeated many times for similarly styled items. For example, the styles for all classes with -success
in their name share the same line for setting the colour. It would be nearly impossible to actually build a CSS library as large as Bootstrap by hand. That’s why it’s not written by hand, instead, it’s written in Sass. Sass is a language I like to think of as CSS with superpowers because it supports things like nesting, complex variables, and looping. Sass files get compiled down to CSS files, which is why Sass is referred to as a CSS pre-processor. Simple Sass files can be used to generate complex CSS files.
To effectively customise Bootstrap you need to use the source Sass files as your starting point. You then make your additions and changes in Sass, and render your custom version of Bootstrap to CSS.
Due to the fact that the default Jekyll theme uses CSS class names that clash with Bootstrap’s CSS class names, we can’t just add Bootstrap to the default theme. Instead, we’re going to need to create our own theme. Don’t worry, this is not difficult to do.
Enable Advanced Sass Processing on our Site
Jekyll has native Sass support, so it has almost everything we need to compile the Bootstrap Sass files to CSS for us. Almost, but not quite! It’s just missing one important feature — a so-called autoprefixer for automatically handling browser-specific quirks through vendor prefixes when compiling Sass to CSS. Thankfully there’s a Jekyll plugin available to provide this functionality, it’s just not installed by default.
Before we can configure the plugin we need to install it locally with the following command from /docs
:
bundle add jekyll-autoprefixer --group jekyll_plugins
As of March 2025, there’s a known issue with this plugin that requires a specific version of the execjs
gem to be used, so add that too with the command:
bundle add execjs -v 2.7
Then we can enable and configure the plugin in our site settings. Update docs/_config.yml
to the following (in the resources as _config-2.yml
):
# Site Settings
title: PBS 177 Test Site
# Build Settings
plugins:
- jekyll-autoprefixer
# Settings for the autoprefixer plugin
autoprefixer:
browsers:
- last 4 versions
- Safari > 2
Add the Needed Bootstrap Files
We’re now ready to download the source version of Bootstrap and add it to our site.
On the Bootstrap download page, click the button to Download source and extract the resulting ZIP file.
Before we add the Bootstrap code, let’s take a moment to learn about two important folder locations used by Jekyll that relate to JavaScript, CSS & Sass files.
- By convention, and as per the examples in the Jekyll documentation, the
assets
folder is used to store files referenced in your theme like Javascript files, cascading style sheets (CSS), and images like icons & logos. Another convention, though not quite as universal, is to organise theassets
folder into type-specific subfolders. We’ll be following this convention by using:assets/js
for our JavaScript files.assets/css
for our generated CSS. But, note that we’ll be writing our CSS as Sass, so the file extension on the files we create will be.scss
. Jekyll will convert them to plain CSS when it builds the site, so the files that appear in the generated website will have their file extensions changed from.scss
to.css
.
- The
_sass
folder is used to store additional Sass imports, this is to say Sass helper files referenced inassets/css/*.scss
files using the@import
Sass directive.
I want to draw attention to two very important things to understand about using Jekyll to convert SASS to CSS:
- Assets with the
.scss
file extension that contain front matter (usually blank) are compiled to CSS automatically. This means that a file namedassets/css/style.scss
with front matter will get converted toassets/css/style.css
, and your theme’s HTML needs to reference the file with that.css
file extension for it to be successfully used. But, if you forget to add front matter, the Sass conversion will not happen! - The entry points to your Sass code must be
assets
, so sticking to best practices, that means you add your primary Sass file(s) toassets/css
. Within those primary Sass files, each time you call@import
, Sass will try to find the requested Sass utility code in the_sass
folder.
Putting it all together, we need to create a primary Sass file in docs/assets/css
, save Bootstrap’s Sass files in docs/_sass
, and Bootstrap’s JavaScript files in docs/assets/js
.
In your local clone of your repository:
- Create a folder named
docs/_sass/bootstrap
and copy the entire contents of thescss
folder from the downloaded and extracted Bootstrap source ZIP here. - Create a folder named
docs/assets/js
and copy both thedist/js/bootstrap.bundle.min.js
anddist/js/bootstrap.bundle.min.js.map
files from the Bootstrap source ZIP here.
Because Bootstrap’s Sass code contains a folder named vendor
which often needs to be ignored in Jekyll sites, it is included in GitHub’s ignore template for Jekyll which we chose to use when creating our repo. This means we need to add an exception for just Bootstrap’s vendor
folder to our .gitignore
. As described in instalment PBS 120 in our long Git series, exceptions to being ignored also get defined in the .gitignore
file, but they need to come after the lines that would otherwise cause them to be ignored. Exceptions are pre-fixed with an (!
). So, all we need to do is add the following to the end of our ignore file:
!/docs/_sass/bootstrap/vendor/
You can do that by editing the .gitignore
file manually, by using your Git GUI app of choice, or by running the following command:
echo '!/docs/_sass/bootstrap/vendor/' >> ../.gitignore
We’re now ready to create a Sass entry point that imports Bootstrap, and, for now, does nothing more. Create a file named docs/assets/css/style.scss
with the following contents (assets-css-style-1.scss
in the resources):
---
---
@import "bootstrap/bootstrap";
At this point, if we asked Jekyll to rebuild our site, all of Bootstrap would appear in our generated site as assets/css/style.css
.
Create our Very Basic Custom Theme
Finally, we need to create the simplest possible custom theme and include the Bootstrap CSS in it.
Create a file named docs/_layouts/default.html
with the following content (in the resources as _layouts-default-1.html
):
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ page.title }} | {{ site.title }}</title>
<link rel="stylesheet" href="{{ '/assets/css/style.css' | relative_url }}">
</head>
<body>
<main class="container pt-3">
{{ content }}
</main>
<script src="{{ '/assets/js/bootstrap.bundle.min.js' | relative_url }}"></script>
</body>
</html>
As you can see, this file contains mostly just regular HTML, but it does contain a few Liquid template strings (the sections wrapped in doubled curly braces, i.e. {{ … }}
). For now, I’m going to ask you to accept this syntax as magic sauce that makes the theme work, but we will learn all about Liquid templates as the series progresses.
Verify the Changes
With these changes made, we can test our site locally with the bundle exec jekyll serve
command, and you’ll see it now looks like a regular Bootstrap page.
Commit all changed files and push to GitHub. Watch the build action as normal, and then verify that the published site is now successfully using our basic Bootstrap theme.
Step 8 — Test Bootstrap
The fact that the regular Bootstrap CSS has been successfully generated and included in the template can be inferred from the fact that the page is rendering in Bootstrap’s default sans-serif font, but that doesn’t prove that we have successfully achieved the ability to customise Bootstrap, or, that Bootstrap’s JavaScript has been successfully incorporated into our theme. Let’s demonstrate both of those things in turn by making some temporary edits to our base theme.
Verifying Bootstrap Customisability
One of the mechanisms by which Bootstrap can be customised is by overriding the value of one of its Sass variables. As an example, let’s update the variables that control the body background and body text colours. Open docs/assets/css/style.scss
and edit it to add two variable definitions before the line that imports Bootstrap. The updated file should look like the following (assets-css-style-2.scss
in the resources):
---
---
/* Override Bootstrap Variables */
$body-bg: LightSkyBlue;
$body-color: MidnightBlue;
/* Import Bootstrap */
@import "bootstrap/bootstrap";
Saving this file and regenerating your site locally will now show dark blue text on a light blue background.
Verify Bootstrap JavaScript
To verify that Bootstrap’s JavaScript is being correctly loaded by the theme, we’ll add a dismissible Bootstrap alert to the top of our theme. Edit docs/_layouts/default.html
to add the following alert at the top of the <main>
tag:
<div class="alert alert-info alert-dismissible fade show" role="alert">
Click my close button to prove that Bootstrap JavaScript is working!
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
The complete file should now contain (_layouts-default-2.html
in the resources):
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ page.title }} | {{ site.title }}</title>
<link rel="stylesheet" href="{{ '/assets/css/style.css' | relative_url }}">
</head>
<body>
<main class="container pt-3">
<div class="alert alert-info alert-dismissible fade show" role="alert">
Click my close button to prove that Bootstrap JavaScript is working!
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{{ content }}
</main>
<script src="{{ '/assets/js/bootstrap.bundle.min.js' | relative_url }}"></script>
</body>
</html>
Rebuild your site locally, then open it in your browser. You should see an alert with a close button. Clicking the close button should dismiss the alert.
A Final GitHub Pages Check
As a final sanity check, commit these changes and push them to GitHub. Wait for the site to rebuild, and then verify that the colour change and alert dismissal are behaving the same in GitHub Pages as they are locally.
Final Thoughts
This instalment was about building a scaffolding to get us started. We’ve left a lot of proverbial mystery meat, which always makes me a little uncomfortable. But, we can now get a basic a basic Jekyll site with a custom theme using customisable Bootstrap up and running both locally and on GitHub Pages. This gives us the ability to develop our site off-line, and to publish updates to the internet as and when we think our work is in an appropriate state to do so.
We’re now ready to start adding our content and evolving our theme. The next few instalments are going to be a little bit challenging because we’ll need to use a little bit of many new technologies all at once. Inevitably there’ll need to be a little hand-waving about a few details initially, but, rest assured, all that detail will be filled in before we finish the series!