From Hugo to Jekyll
Hello everyone! 👋
A small update for the blog, I’ve moved it from Hugo1 to Jekyll2.
Introduction
Hugo is a static website generator just like Jekyll, it’s written in Go, and it’s pretty fast. It is also distributed as a binary.
However, that didn’t stop me to go back to Jekyll, an older static website generator written in Ruby.
The main reason for the update is that I love the Minimal Mistakes3 theme written by Michael Rose and I like the Ruby ecosystem (bundler, rake) and that’s about it.
Here’s what I did not like about Hugo.
I don’t care about the speed since for my blog Jekyll builds in about 10s and Hugo is 10x faster. Regarding the binary distribution I always had to install a lot of dependencies in order to build hugo-extended version because it most likely wasn’t packaged for my Linux distro.
Sometimes when I updated either Hugo or my theme I got build failures and I had to sync the theme version and Hugo version, and sometimes this would break my changes.
I also didn’t like that I had to specify an image url instead of a file url when using a hero image under the Blowfish[4] theme.
What I loved about Hugo is the performance, the fact that it is written in Go, and it’s internationalization capabilities.
Jekyll is not perfect, but it works well for my use case. I also like having a Rakefile4 that I use it to build my blog
under Linux and Windows and upload it to my server via rsync5:
desc "Serve the site locally"
task :serve do
sh "bundle exec jekyll serve --livereload"
end
desc "Build and deploy the site via rsync"
task :deploy => :build do
is_windows = Gem.win_platform?
sync_destination = "[email protected]:/blog"
if is_windows
sh "wsl rsync -arv -e 'ssh -i ~/.ssh/nuculabs.dev' ./_site/ #{sync_destination}"
else
sh "rsync -arv -e ssh ./_site/ #{sync_destination}"
end
end
Customizations
I started doing some custom customizations on the blog in order to make it look better. There are small improvements for table of contents, the hero image and the footer.
Page Hero, ToC and Footer
You can use my custom.sass file:
.footer-flex
display: flex
justify-content: space-between
// Page hero modifications
.page__hero
position: absolute
.page__hero-image
opacity: 0.85
width: 100vw
&::after
content: ""
position: absolute
top: 0
left: 0
width: 100%
height: 100%
// Center is transparent (0),
// Edges are White (1)
background: linear-gradient(to bottom, transparent 70%, rgba(255,255,255,1) 100%), radial-gradient(ellipse 50% 100% at center, rgba(255,255,255,0.8) 0%, rgba(255,255,255,0.8) 45%, rgba(255,255,255,0) 100%)
pointer-events: none
// Toc improvement
.toc:not(:has(> ul))
display: none
.btn--mastodon
background-color: #6147e6
color: #fff
&:visited, &:hover, &:focus, &:active
background-color: #6147e6
color: #fff
.btn--reddit
background-color: #ff4500
color: #fff
&:visited, &:hover, &:focus, &:active
background-color: #ff4500
color: #fff
Don’t forget to import it in the main scss file.
@import "custom"; // custom styles _sass/custom.sass
Social Share
I’ve also modernized the social share buttons because I like having Mastodon and Bluesky as an option.
Here are the contents of _includes/social-share.html.
<section class="page__share">
<h4 class="page__share-title">Share on</h4>
<a href="https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fnuculabs.dev%2Fweb%2520development%2F2026%2F02%2F24%2Ffrom-hugo-to-jekyll.html"
class="btn btn--facebook" aria-label="Share on Facebook"
onclick="window.open(this.href, 'window', 'left=20,top=20,width=500,height=500,toolbar=1,resizable=0'); return false;"
title="Share on Facebook">
<i class="fab fa-fw fa-facebook" aria-hidden="true"></i><span> Facebook</span>
</a>
<a href="https://www.linkedin.com/shareArticle?mini=true&url=https://nuculabs.dev/web%20development/2026/02/24/from-hugo-to-jekyll.html"
class="btn btn--linkedin" aria-label="Share on LinkedIn"
onclick="window.open(this.href, 'window', 'left=20,top=20,width=500,height=500,toolbar=1,resizable=0'); return false;"
title="Share on LinkedIn">
<i class="fab fa-fw fa-linkedin" aria-hidden="true"></i><span> LinkedIn</span>
</a>
<a href="https://bsky.app/intent/compose?text=From+Hugo+to+Jekyll%20https%3A%2F%2Fnuculabs.dev%2Fweb%2520development%2F2026%2F02%2F24%2Ffrom-hugo-to-jekyll.html"
class="btn btn--bluesky"
onclick="window.open(this.href, 'window', 'left=20,top=20,width=500,height=500,toolbar=1,resizable=0'); return false;"
title="Share on Bluesky">
<i class="fab fa-fw fa-bluesky" aria-hidden="true"></i><span> Bluesky</span>
</a>
<a
href="https://s2f.kytta.dev/?text=From+Hugo+to+Jekyll%20https%3A%2F%2Fnuculabs.dev%2Fweb%2520development%2F2026%2F02%2F24%2Ffrom-hugo-to-jekyll.html"
class="btn btn--mastodon"
onclick="
window.open(
this.href,
'window',
'left=20,top=20,width=500,height=500,toolbar=1,resizable=0',
);
return false;
"
title="Share on Mastodon"
>
<i class="fab fa-fw fa-mastodon" aria-hidden="true"></i
><span> Mastodon</span>
</a>
<a
href="https://www.reddit.com/submit/?url=https%3A%2F%2Fnuculabs.dev%2Fweb%2520development%2F2026%2F02%2F24%2Ffrom-hugo-to-jekyll.html&resubmit=true&title=From+Hugo+to+Jekyll"
class="btn btn--reddit"
onclick="
window.open(
this.href,
'window',
'left=20,top=20,width=500,height=500,toolbar=1,resizable=0',
);
return false;
"
title="Share on Reddit"
>
<i class="fab fa-fw fa-reddit" aria-hidden="true"></i
><span> Reddit</span>
</a>
</section>
Lightbox
Having a lightbox feature is useful if you serve pictures on your blog, the following JS and CSS makes so that when you click on an image it resizes it and makes the background darker. I did not write the original script but I have adapted it to work with the Minimal Mistakes theme.
I use plain css:
#lightbox {width: 100%; height: 100%; position: fixed; top: 0; left: 0; background: rgba(0,0,0,0.85); z-index: 9999999; line-height: 0; cursor: pointer; display: none;}
#lightbox .img {
position: relative;
top: 50%;
left: 50%;
-ms-transform: translateX(-50%) translateY(-50%);
-webkit-transform: translate(-50%,-50%);
transform: translate(-50%,-50%);
max-width: 100%;
max-height: 100%;
}
#lightbox .img img {opacity: 0; pointer-events: none; width: auto;}
@media screen and (min-width: 1200px) {
#lightbox .img {
max-width: 1200px;
}
}
@media screen and (min-height: 1200px) {
#lightbox .img {
max-height: 1200px;
}
}
#lightbox span {display: block; position: fixed; bottom: 13px; height: 1.5em; line-height: 1.4em; width: 100%; text-align: center; color: white; text-shadow:
-1px -1px 0 #000,
1px -1px 0 #000,
-1px 1px 0 #000,
1px 1px 0 #000;
}
#lightbox span {display: none;}
#lightbox .videoWrapperContainer {
position: relative;
top: 50%;
left: 50%;
-ms-transform: translateX(-50%) translateY(-50%);
-webkit-transform: translate(-50%,-50%);
transform: translate(-50%,-50%);
max-width: 900px;
max-height: 100%;
}
#lightbox .videoWrapperContainer .videoWrapper {
height: 0;
line-height: 0;
margin: 0;
padding: 0;
position: relative;
padding-bottom: 56.333%; /* custom */
background: black;
}
#lightbox .videoWrapper iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 0;
display: block;
}
#lightbox #prev, #lightbox #next {height: 50px; line-height: 36px; display: none; margin-top: -25px; position: fixed; top: 50%; padding: 0 15px; cursor: pointer; text-decoration: none; z-index: 99; color: white; font-size: 60px;}
#lightbox.gallery #prev, #lightbox.gallery #next {display: block;}
#lightbox #prev {left: 0;}
#lightbox #next {right: 0;}
#lightbox #close {height: 50px; width: 50px; position: fixed; cursor: pointer; text-decoration: none; z-index: 99; right: 0; top: 0;}
#lightbox #close:after, #lightbox #close:before {position: absolute; margin-top: 22px; margin-left: 14px; content: ""; height: 3px; background: white; width: 23px;
-webkit-transform-origin: 50% 50%;
-moz-transform-origin: 50% 50%;
-o-transform-origin: 50% 50%;
transform-origin: 50% 50%;
/* Safari */
-webkit-transform: rotate(-45deg);
/* Firefox */
-moz-transform: rotate(-45deg);
/* IE */
-ms-transform: rotate(-45deg);
/* Opera */
-o-transform: rotate(-45deg);
}
#lightbox #close:after {
/* Safari */
-webkit-transform: rotate(45deg);
/* Firefox */
-moz-transform: rotate(45deg);
/* IE */
-ms-transform: rotate(45deg);
/* Opera */
-o-transform: rotate(45deg);
}
#lightbox, #lightbox * {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
And plain javascript:
function is_youtubelink(url) {
var p = /^(?:https?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})(?:\S+)?$/;
return (url.match(p)) ? RegExp.$1 : false;
}
function is_imagelink(url) {
var p = /([a-z\-_0-9\/\:\.]*\.(jpg|jpeg|png|gif))/i;
return (url.match(p)) ? true : false;
}
function is_vimeolink(url,el) {
var id = false;
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == XMLHttpRequest.DONE) { // XMLHttpRequest.DONE == 4
if (xmlhttp.status == 200) {
var response = JSON.parse(xmlhttp.responseText);
id = response.video_id;
console.log(id);
el.classList.add('lightbox-vimeo');
el.setAttribute('data-id',id);
el.addEventListener("click", function(event) {
event.preventDefault();
document.getElementById('lightbox').innerHTML = '<a id="close"></a><a id="next">›</a><a id="prev">‹</a><div class="videoWrapperContainer"><div class="videoWrapper"><iframe src="https://player.vimeo.com/video/'+el.getAttribute('data-id')+'/?autoplay=1&byline=0&title=0&portrait=0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe></div></div>';
document.getElementById('lightbox').style.display = 'block';
setGallery(this);
});
}
else if (xmlhttp.status == 400) {
alert('There was an error 400');
}
else {
alert('something else other than 200 was returned');
}
}
};
xmlhttp.open("GET", 'https://vimeo.com/api/oembed.json?url='+url, true);
xmlhttp.send();
}
function setGallery(el) {
var elements = document.body.querySelectorAll(".gallery");
elements.forEach(element => {
element.classList.remove('gallery');
});
if(el.closest('ul, p')) {
var link_elements = el.closest('ul, p').querySelectorAll("a[class*='lightbox-']");
link_elements.forEach(link_element => {
link_element.classList.remove('current');
});
link_elements.forEach(link_element => {
if(el.getAttribute('href') == link_element.getAttribute('href')) {
link_element.classList.add('current');
}
});
if(link_elements.length>1) {
document.getElementById('lightbox').classList.add('gallery');
link_elements.forEach(link_element => {
link_element.classList.add('gallery');
});
}
var currentkey;
var gallery_elements = document.querySelectorAll('a.gallery');
Object.keys(gallery_elements).forEach(function (k) {
if(gallery_elements[k].classList.contains('current')) currentkey = k;
});
if(currentkey==(gallery_elements.length-1)) var nextkey = 0;
else var nextkey = parseInt(currentkey)+1;
if(currentkey==0) var prevkey = parseInt(gallery_elements.length-1);
else var prevkey = parseInt(currentkey)-1;
document.getElementById('next').addEventListener("click", function() {
gallery_elements[nextkey].click();
});
document.getElementById('prev').addEventListener("click", function() {
gallery_elements[prevkey].click();
});
}
}
document.addEventListener("DOMContentLoaded", function() {
//create lightbox div in the footer
var newdiv = document.createElement("div");
newdiv.setAttribute('id',"lightbox");
document.body.appendChild(newdiv);
//add classes to links to be able to initiate lightboxes
var elements = document.querySelectorAll('.page__content a');
elements.forEach(element => {
var url = element.getAttribute('href');
if(url) {
if(url.indexOf('vimeo') !== -1 && !element.classList.contains('no-lightbox')) {
is_vimeolink(url,element);
}
if(is_youtubelink(url) && !element.classList.contains('no-lightbox')) {
element.classList.add('lightbox-youtube');
element.setAttribute('data-id',is_youtubelink(url));
}
if(is_imagelink(url) && !element.classList.contains('no-lightbox')) {
element.classList.add('lightbox-image');
var href = element.getAttribute('href');
var filename = href.split('/').pop();
var split = filename.split(".");
var name = split[0];
element.setAttribute('title',name);
}
}
var imageElements = document.querySelectorAll(".page__content img");
imageElements.forEach( element => {
element.classList.add('lightbox-image');
})
});
//remove the clicked lightbox
document.getElementById('lightbox').addEventListener("click", function(event) {
if(event.target.id != 'next' && event.target.id != 'prev'){
this.innerHTML = '';
document.getElementById('lightbox').style.display = 'none';
}
});
//add the youtube lightbox on click
var elements = document.querySelectorAll('a.lightbox-youtube');
elements.forEach(element => {
element.addEventListener("click", function(event) {
event.preventDefault();
document.getElementById('lightbox').innerHTML = '<a id="close"></a><a id="next">›</a><a id="prev">‹</a><div class="videoWrapperContainer"><div class="videoWrapper"><iframe src="https://www.youtube.com/embed/'+this.getAttribute('data-id')+'?autoplay=1&showinfo=0&rel=0"></iframe></div>';
document.getElementById('lightbox').style.display = 'block';
setGallery(this);
});
});
//add the image lightbox on click
var elements = document.querySelectorAll('a.lightbox-image');
elements.forEach(element => {
element.addEventListener("click", function(event) {
event.preventDefault();
document.getElementById('lightbox').innerHTML = '<a id="close"></a><a id="next">›</a><a id="prev">‹</a><div class="img" style="background: url(\''+this.getAttribute('href')+'\') center center / contain no-repeat;" title="'+this.getAttribute('title')+'" ><img src="'+this.getAttribute('href')+'" alt="'+this.getAttribute('title')+'" /></div><span>'+this.getAttribute('title')+'</span>';
document.getElementById('lightbox').style.display = 'block';
setGallery(this);
});
});
var elements = document.querySelectorAll('img.lightbox-image');
elements.forEach(element => {
element.addEventListener("click", function(event) {
event.preventDefault();
document.getElementById('lightbox').innerHTML = '<a id="close"></a><a id="next">›</a><a id="prev">‹</a><div class="img" style="background: url(\''+this.getAttribute('src')+'\') center center / contain no-repeat;" title="'+this.getAttribute('title')+'" ><img src="'+this.getAttribute('src')+'" alt="'+this.getAttribute('title')+'" /></div><span>'+this.getAttribute('title')+'</span>';
document.getElementById('lightbox').style.display = 'block';
setGallery(this);
});
});
});
Conclusion
Hugo is faster and newer than Jekyll but if you like the Ruby ecosystem you can give Jekyll a try. The Minimal Mistakes theme by Michael Rose still rocks!