My experience building this website with Jekyll and GitHub Pages
•
2 August 2025
•
25 mins
I started this as a test blog post so I have some content to play around with while figuring out how to build this site. However, as this is the first time I ever build a website or use Jekyll, I thought my process of figuring stuff out would be a good opportunity to document what I’m learning as I go.
Using GitHub Pages as a website platform
One of the main driving factors in choosing Jekyll is that it is very easy
to publish the website on GitHub pages.
One should note that it is better to use the
Jekyll workflow action
rather than the one provided by github as it builds the site in a docker image and
is not restricted by the default github pages limitations (use of plugins, old
version of Jekyll etc.).
Markdown content
I’ve never been a huge fan of Markdown, probably because I’ve been spoiled by emacs org-mode, and I often get the syntax confused. In the future, I will probably figure out how to automatically publish Jekyll-buildable html directly from org-mode, but for now, markdown will have to do - and it sure beats writing directly with html.
Tweaking the theme
To get started with building the actual site, I thought I’d best find a decent
theme I like and use it as a base for tweaking. This of course has its ups and
downs, but I have no aspiration of becoming a web developer, so I think it is
the fastest path to getting the desired outcome, which is this site. I choose
Awesome Jekyll Theme for
this purpose, with clear intent to modify it.
This leads to some caveats: everything from here on is based on modifying
“this” theme, which comes with a pre-built Tailwind css present from the Gem
install, and makes the assumption that all the Tailwind features used
are present. Replicating everything below might not work exactly the same
way with a different theme, so please keep that in mind.
I decided to go down this path mainly because it is really convenient being able to add only the html and css files I want to change and having them override the default ones installed (not without some difficulties as you will read about later).
To get started, I made just a few small tweaks. First, I changed the navbar to be sticky for simpler navigation. For this, I ended up needing some additional custom css to overwrite the default behaviour.
nav.sticky,
.sticky-nav {
position: -webkit-sticky !important;
position: sticky !important;
top: 0 !important;
z-index: 999 !important;
background-color: inherit !important;
}
I wrote this to assets/css/custom.css and loaded this as an additional stylesheet
in _layouts/default.html , then added the class to nav object in
_includes/navbar.html.
<nav class="sticky top-0 left-0 right-0 z-50 flex w-full flex-wrap items-center justify-between py-4 bg-white dark:bg-neutral-900 shadow-md">
Some other changes I made were to remove the ‘pages’ from the footbar (as I
preder to navigate from the navbar, which is now sticky), and ensure the titles
in the _layouts/page.html changed colour when switching between dark and light
themes. This last issue actually seems like a bug, so maybe I should open a PR
with this suggested fix. It was only changing
<h1 class="text-left text-2xl font-bold md:text-5xl text-black">{{page.title}}</h1>
to
<h1 class="text-left text-2xl font-bold md:text-5xl text-neutral-900 dark:text-neutral-200">{{page.title}}</h1>
I then went ahead and changed the home page to remove the side-scroll buttons and made it more fluid to match my design intension which was pretty straight forward all things considered. It was a bit of a pain to teak to spacing, but eventually I got there.
Adding contact_channels
I wanted to add some more channels I am on which are not part of the theme.
Compared to other things, this was quite straight forward, and I got to learn about
simpleicons.org which I never knew about before!
All it took was to copy the one of the existing blocks in
_includes/contact_channels.html and replace the label and
img. For example, for ORCID I added this code block, with the .svg saved in the
corresponding directory.
{% endif %} {% if contact_channels.orcid %}
<a
class="transition hover:scale-110 duration-300"
href="{{ contact_channels.orcid }}"
target="_blank"
rel="noopener noreferrer"
aria-label="ORCID"
>
<img
class="h-6 w-6 dark:invert"
src="{{ '/assets/images/contact_channels/orcid.svg' | relative_url }}"
alt="ORCID Icon"
/>
</a>
I ended up adding Google Scholar, ORCID and ResearchGate to the list.
Robots and maps
I asked a web developer friend of mine for some feedback and he suggested I should
add robots.txt and a sitemap. I had no idea these things even exist! Luckily,
with Jekyll this was quite easy.
I added the sitemap with a Jekyll plugin, jekyll-sitemap, in the _config.yml
and Gemfile. That automatically generates the sitemap.
Next is robots.txt, which suggests to crawlers what to index in the site.
From what I understood, I only need a very basic one, so I just added
User-agent: *
Disallow:
Sitemap: {{ site.url }}/sitemap.xml
and saved it in the root directory. As I need the liquid tag (i.e. the things in
{{ }}) to get built by liquid, I added this line to the _config.yml
include:
- robots.txt
These two things should now ensure that the sitemap is built and that it accessible by crawlers.
Meta-title and favicon
Again, new concepts I never heard of before. These too were pretty straight forward
to add.
The meta-title is the title you see in the tabs. I set it with some unicode to fit
the style I’m going for in this site. I just added the following like to the
<head> of the default layout
<title>mdg ❯ My experience building this website with Jekyll and GitHub Pages</title>
This again uses liquid to find the title, and I just added a permanent “mdg ❯” before it as I think it makes more sense than just a title like “about”.
The favicon is the icon in the tab / bookmark etc. which can be quite useful! I experimented with using the logo as a favicon but did not like it, so for now I do not have one. However, to add it is as simple as adding a line again in the head of the default layout
<link rel="icon" href="/assets/favicon.ico" type="image/x-icon"
More fighting with Tailwind
In general, the thing I found the hardest in building this site is that a lot of
what I read online about how to change things in html, Flexbox and Liquid did
not seem to work. The theme ships with a pre-built Tailwind dist-style.css, and
if I understood correctly (which I very well might not have!), as I am not building
the Tailwind styles, the Tailwind JIT compiler purged any unused styles from the
pre-built css. Hence, when I tried to use styles that were not there at build time,
their css could not be found and they could not be applied by the browser.
The really issue was that I never really knew which styles were actually available
to me and would work when used in the html, and which would not.
To overcome this limitation, I ended up having to place most changes in the
custom css file in a similar way to the sticky navbar. This included a bunch of
issues with controlling the footer and the navbar in particular.
As an example, to get the a page to fill the screen and to keep the footer at the
bottom of the screen when it under filled, I added a Flexbox to the body with
min-h-screen to the body will fill the viewport, then wrapped the content with
a <main class="flex-grow">
<body class="flex flex-col min-h-screen bg-neutral-50 text-neutral-900 dark:bg-neutral-950 dark:text-neutral-200">
{% include navbar.html %}
<main class="flex-grow">
{{ content }}
</main>
{% include footer.html %}
</body>
This should use Flexbox to make the main area grow and take up all the space not
occupied by the footer in the viewport, so it pushes the footer to the bottom when
there is extra vertical space.
What was needed next is to add margin-top: auto to the footer in
assets/css/custom.css. At first that did not seem to work, so I had to also add
the following
body > footer {
margin-top: auto !important;
}
Notice the body > footer bit, which makes sure the css treats the footer as a direct
child of body when applying the top margin. From my understanding, it already
should do that if I built the styles with Tailwind myself — but as I could not,
it had to be added this way instead. Finding out such limitations really took me
some time and a lot of trial and error.
Similarly, to gain finer control over other layouts, like the top padding and
the width in the page layout, I could not simply modify the class style in the
layout’s html by changing the values in pt-20 lg:pt-[90px], but instead had
to add it to the page layout class without Tailwind commands through the
assets/css/custom.css like this
#page {
padding-top: 25px;
max-width: 90ch;
}
Things got much harder when I tried to reduce the footer size. I decided I wanted all elements of the footer aligned at the bottom, with the contact channels to the left, copyright notice in the centre, and rss subscription in the right.
This should be simple enough, but figuring out how to untangle it from the
current pre-built styles was not so easy. I ended up using a Flexbox container
which finally did the trick, as adapting the existing style with Tailwind just
did not work. I also wanted to make sure that in mobile view the footer gets
stacked and changes the order so that the rss subscription is in between the others.
To do that, I added some classes to the custom.css file:
.mobile-only {
display: block;
}
.desktop-only {
display: none;
}
@media (min-width: 768px) {
.mobile-only {
display: none !important;
}
.desktop-only {
display: block !important;
}
}
@media (min-width: 768px) {
.footer-copyright {
align-self: flex-end; /* Desktop only */
}
}
These classes are then used in the rss subscription block and change its display
property based on the width of the window. The footer-copyright is added as an
additional class with align-self: flex-end to ensure the copyright is aligned
to the bottom better, and also ensures that it only applies on Desktop so that it
doesn’t break the alignment on mobile.
Theme-aware logo
So I am no designer, but I did want to add some flare to the site. I thought of
the simplest logo possible that would match the site - a terminal prompt with my
initials.
Finding a prompt icon and adding letters was easy enough, but to get it to work
with the theme properly, I needed to get it in a simple .svg format that I can
embed in the html so that the theme can affect it directly. I used Inkscape to
trace the logo, then edited the XML of the .svg to remove fill and size
information as these will be controlled properly by css using the theme’s
currentColor so it adapts with the theme switcher for dark mode, as well
as browser extensions like Dark Reader.
.logo-image {
color: inherit;
display: inline-block;
overflow: visible;
max-width: 100%;
}
.logo-image svg path {
fill: currentColor;
}
.logo-image svg {
height: 1.6rem;
width: auto;
display: block;
padding-top: 0.22rem;
overflow: visible;
shape-rendering: geometricPrecision;
}
Of course, I still had to inline the icon itself. One option was to add
it to the _include directory and use liquid to inline it, but I really
didn’t want to have a .svg there. Instead, I wrote a simple ruby plugin
to read and inline it that works perfectly with Jekyll.
# _plugins/inline_svg.rb
module Jekyll
module InlineSVG
def inline_svg(path)
file_path = File.join(@context.registers[:site].source, path)
File.read(file_path)
end
end
end
Liquid::Template.register_filter(Jekyll::InlineSVG)
Custom plugins can be added to the _plugins directory and will automatically
be added to the project, so that’s really cool! With this plugin, I could
inline the logo with in _includes/navbar.html
<div class="logo-image">
{{ '/assets/images/logo.svg' | inline_svg }}
</div>
I also wanted to change how the breadcrumbs look, so I added this to the navbar as well
<p class="pl-4 hidden md:block text-lg font-bold">
<span class="text-mulish text-lg font-normal">~/posts/first-steps-with-jekyll/</span>
</p>
Now that looks like a prompt navigation in the navbar - lovely!
Better responsive design
The theme, as lovely as it is, does not have a burger menu for mobile mode, so that had to be added as well.
As before, I added some css to custom.css
@media (min-width: 768px) {
.mobile-burger {
display: none;
}
.desktop-nav {
display: flex !important;
width: auto !important;
}
.nav-links-container {
flex-direction: row;
padding-top: 0;
}
}
With this in place, I changed the navbar with the following. First, I added the
button itself with the mobile-burger class that hides the button unless in
mobile view.
<button id="mobile-menu-toggle" class="mobile-burger" aria-label="Toggle mobile menu">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
</svg>
</button>
Then, I added a container that is hidden in mobile view and a Flexbox in Desktop view for the navigation menu itself.
<div id="mobile-menu" class="desktop-nav hidden w-full items-center">
<div class="nav-links-container flex flex-col pt-4">
{% for page in site.pages %}
{% if page.nav %}
<div class="py-2 md:py-0"><a class="px-4 hover:underline {% if page.url == current_page_url %}font-bold{% endif %}"
href="{{ page.url | relative_url }}">{{ page.title | default: page.name }}</a></div>
{% endif%}
{% endfor %}
<!-- theme mode switcher from original code -->
</div>
</div>
The switching ‘on’ of the Flexbox is controlled by the desktop-nav class,
and the mobile view with the nav-links-container class.
Bibtex with jekyll-scholar, ruby plugins and more custom css
The final thing I wanted to get working in the site in a nice publications
page.
Browsing around, the best example I could find was from
Prof. David Moxey (or Dave), my PI during
my last postdoc position. I saw he used
jekyll-scholar, but try as I might,
I could not figure out how to control the bibtex entries, not to mention the badges
to show the BibTeX entry and abstracts, features I absolutely loved and wanted
on my site!
So, I reached out to Dave, and he was kind enough to give me some pointers to
what he has done and granted me access to his site’s repo so I can learn from
the code itself.
With this as a reference, I set out three goals for this section:
- Highlight my name in the reference;
- DOI and URLs should actually be hyperlinks;
- The cool badges for the BibTeX and Abstract windows!
The first thing to do is configure jekyll-scholar with the regular parameters
to format the bibliography entries as html. Going over the docs, it needed
the following added to _config.yml
scholar:
style: _bibliography/ieee.csl
locale: en
source: ./_bibliography
bibliography: mashy.bib
Going over Dave’s site and reading up some more on jekyll-scholar, I realised that
it uses CiteProc-Ruby to parse the
BibTeX and returns formatted html, so it is very hard to interact and adapt it
after it is formated to html. Hence, the way to modify the entries is by
using custom ruby plugins. I shamelessly copied the plugins from Dave and placed
them in _plugins. I only needed two of the plugins: the first is a simple one that
strips the html <div> from the jekyll-scholar reference which I will discuss
later. The other and far more important plugin is highlight_name.rb, which I stripped
down to just do what I wanted
module Jekyll
module HighlightName
def highlightname(input)
# Highlight name
input = input.gsub(/(M\. D\. Green)/, '<strong style="border-bottom: 1px solid #000">\1</strong>')
# Add DOI link if tagged
if input.include?("#doi#")
input = input.gsub(/#doi#(.*?)#edoi#/, '<a href="https://doi.org/\1" target="_blank" rel="noopener noreferrer">\1</a>')
end
# Add URL link if found
if input =~ /Available at:\s+(https?:\/\/[^\s<]+)/
input = input.gsub(/Available at:\s+(https?:\/\/[^\s<]+)/) do
url = Regexp.last_match(1)
%Q{Available at: <a href="#{url}" target="_blank" rel="noopener noreferrer">#{url}</a>}
end
end
end
end
end
Liquid::Template.register_filter(Jekyll::HighlightName)
This plugin dose 3 things. The first is to gobble the text, find my name and change
the text to bold with an underline for better visibility. The second is to look
for custom doi tags #doi# and #edoi#, then create a hyperlink from the text
between them. For that, I had to modify the BibTeX style to add these tags with
<text variable="DOI" prefix=". #doi#" suffix="#edoi#" />
The last relies on the specific formatting in the ieee format for URLs to match “Available at:”, gobble the url text and turn it into a hyperlink.
That takes care of goals 1 and 2. Goal 3 was rather more fun. The way to adapt
the formatting of each reference is using a specific html layout for jekyll-scholar.
To do this, I created a new layout in _layouts/bib.html and added it to the config
scholar:
bibliography_template: bib
Once again, I shameless copied Dave’s code and started adapted it to my needs. A major
point of difference is that Dave was using jQuery, whcih I did not want to install in
the site, to create the animated boxes with slideToggle, so instead I ended up
writing some javascript to replicate that same behaviour., as well as some more custom
css to control the animations.
<div id="{{ key }}">
{{ reference | strip_div }}
{% if entry.bibtex and entry.type != 'speech' %}
<span class="badge badge-bibtex" onclick="slideToggle('bibtex-{{ entry.key }}')">BibTeX</span>
{% endif %}
{% if entry.abstract %}
<span class="badge badge-abstract" onclick="slideToggle('abstract-{{ entry.key }}')">Abstract</span>
{% endif %}
{% if entry.bibtex and entry.type != 'speech' %}
<pre id="bibtex-{{ entry.key }}" class="bibtex-block">{{ entry.bibtex }}</pre>
{% endif %}
{% if entry.abstract %}
<div id="abstract-{{ entry.key }}" class="abstract-block">{{ entry.abstract }}</div>
{% endif %}
</div>
<script>
function slideToggle(id) {
const el = document.getElementById(id);
if (!el) return;
if (el.style.height && el.style.height !== '0px') {
// Collapse
el.style.height = el.scrollHeight + 'px'; // set current height to trigger transition
el.classList.remove('expanded'); // remove gap
requestAnimationFrame(() => {
el.style.height = '0px';
});
} else {
// Expand
el.style.height = '0px'; // first reset to zero
el.classList.add('expanded'); // then add gap
requestAnimationFrame(() => {
el.style.height = el.scrollHeight + 'px';
});
}
}
</script>
This layout is where that first ruby plugin I mentioned earlier to strip the
<div> from the html is used
module Jekyll
module StripDiv
def strip_div(input)
input.gsub(/^<div id=\".*?\">(.*?)<\/div>$/m, '\1')
end
end
end
Liquid::Template.register_filter(Jekyll::StripDiv)
To make sense of the layout, I should also include the css
.badge {
display: inline-block;
padding: 2px 6px;
color: var(--polar-night-0);
font-size: 0.75rem;
font-weight: 600;
border-radius: 4px;
text-decoration: none;
margin-left: 8px;
cursor: pointer;
}
pre.bibtex-block,
div.abstract-block {
background: var(--color-gray-200);
border-left: 4px solid var(--frost-blue);
padding: 0px;
margin: 0;
font-size: 0.9rem;
overflow-x: auto;
color: var(--color-gray-900);
height: 0;
overflow: hidden;
transition: height 0.3s ease, margin-top 0.3s ease;
}
pre.bibtex-block.expanded,
div.abstract-block.expanded {
margin-top: 8px;
}
.badge-bibtex {
background-color: var(--aurora-green);
}
.badge-bibtex:hover {
background-color: color-mix(in srgb, var(--aurora-green) 80%, white);
}
.badge-abstract {
background-color: var(--frost-cyan);
}
.badge-abstract:hover {
background-color: color-mix(in srgb, var(--frost-cyan) 80%, white);
}
Hopefully, after the explanations in the previous sections, this should be clear enough, and as this blogpost is already far too long, I will leave the details of how this all works out. If anyone is interested, feel free to contact me and I can either provide more details in private or try and write a shorter and more comprehensive dedicated post on this specifically.
Final thoughts
I learned a lot in the week I’ve been working on this website and I’m mostly
happy with the result. I found starting from a theme was in fact very limiting
and resulted in me applying ‘fixes’ and ‘patches’ over things rather than
actually designing them that way from the start. I ended up with some probably
over-complicated code. The biggest pain was dealing with Tailwind and Flexbox -
I really only gained a vague idea what they are doing as the theme used a
pre-built Tailwind styles file that provided css for only some class styles
under the hood in a way that was not transparent to me. This really made
granular control of some things I wanted to do much more difficult than it had
to be.
It is entirely possible that if I built everything from the ground-up, these
tools would not have been so difficult to work with.
Saying that, I certainly got a better result within this single week, having no prior web-related development experience at all (not even plain html or css, and certainly nothing with Jekyll, Tailwind or Flexbox), than I would had if I started from scratch. The theme threw me straight into the deep end and I was exposed to a lot more than I would have been otherwise, so I probably also gained quite a lot going with this approach.
So my final conclusion is that as I already spent a full week of my annual leave on this site and it is functioning well enough, I will leave it as it is for now. Next summer, I will use everything I learned this time and design a similar Jekyll-based site from scratch without using any pre-configured themes and hopefully be able to keep things much simpler and leaner.
Until that time, hope you found some things here useful - I certainly did!