Skip to content
How can you tell that a web developer is working?

How can you tell that a web developer is working?

You can hear him Grunting!

The problem

I want my blog (the one your’re reading) to be statically and freely served, using my own domain name. I want it to be maintainable and easy to update. And first of all, I really do not have much time for this, so it must be up and running in a few hours.

The solution

Bye bye WordPress, Welcome Jekyll. I can handle a couple lines of PHP but it's not my cup of tea. And WP breaks the static rule, so as the maintainable one. I previously used Jekyll to create the OctoPerf website. And as you may ask yourself why the bad joke about Grunt: I used it with Bower to create the frontend of our load testing solution.

Jekyll is a really handy tool. The kind of tool that you can use even without first reading the documentation (my preferred kind!). And when you face a problem, the solution is placed in the first 3 results of Google. The first one often being their manual that you didn't read.
But Jekyll doesn't come with dependencies management, and the couple Grunt/Bower is to JavaScript what Maven or Ant/Ivy is to Java: a nice build solution.

Do YOU have custom Load Testing needs?
Rely on our Expertise

To create this blog I mixed the three of them, and have a Jekyll boosted with:

  • JS dependencies managed by Bower,
  • CSS and JS files references automatically included in my HTML pages.

On top of that I wanted to give a try to Bootstrap4 and host it on GitHub pages. Bootstrap because I can't do anything Web-related without it, and GitHub pages to freely host my blog using my own domain name.

So stop being soiled by dirty PHP/MySQL and let's get started with static websites.

Blog template

Even so my blog is on a private repository, a public template is available. If you too are interested in creating yours this way, you may read this article. You can also fork the blog-template repository, use it for your own and skip the rest of the reading. The README contains all the commands you may need to run and deploy it.

Well ... I bet you'll come back at least once on this page to get more information about the template. But go on, fork it and build it by yourself, I would have done the same ;)

To save you some of your precious time:

  • If you're not familiar with it, you may still need to read the GitHub Pages part to deploy your blog on it.
  • If you're lost within the Bower and Grunt configurations, provided that you dare edit it, head back to the corresponding chapter.
  • The rest of the article explain how I went from the default Jekyll template to a more advanced theme based on bootstrap 4, you may read it before customizing the CSS of your blog.

Prerequisites / installation

The prerequisites to build and host this blog are:

  • A Linux or Mac operating system (I run on Ubuntu, so all commands are for this specific OS),
  • Jekyll our static website generator (sudo apt-get install ruby-full rubygems-integration and sudo gem install jekyll),
  • Node.js and its package manager Npm (sudo apt-get install nodejs npm),
  • Grunt, our Javascript task runner (sudo npm install -g grunt-cli),
  • Bower to manage the blog dependencies (sudo npm install -g bower).

The coffee-script npm module should also be installed globally: sudo npm install -g coffee-script.

You may also need to run the command ln -s /usr/bin/nodejs /usr/bin/node if you have the error /usr/bin/env: node: No such file or directory when running grunt or bower.

But first of all, you need a GitHub account using SSH Keys to host it (or an Amazon account if you prefer to host Jekyll on CloudFront)

GitHub pages

I first wanted to create my blog as a user site. So I created a geraldpereira repository but failed to configure my DNS to use a custom URL in less than 20 minutes. Remember, I don't have to much time to devote to this. I quickly gave up and created a private blog repository.

Then I used the auto-generated pages :

  1. Go to the settings of your blog repo:\<username>/blog/settings or click on the button located on the right menu of your repository page.
  2. Into the GitHub Pages section click on Launch Automatic Page generator.
  3. Click on as we will then replace the generated content by our Jekyll blog.
  4. Select any layout and click on .
  5. Visit\<username>/blog/tree/gh-pages or select the gh-pages branch in your blog repo. You should see the source files of the generated content.
  6. Visit http://\<username> to check that the blog is available.


The master branch is private (for a private repo), but the gh-pages branch is always public on GitHub.

This procedure can be skipped if you prefer to directly create a gh-pages branch on your blog repository. For once I preferred to follow the GitHub tutorial.

I do not use GitHub's embedded Jekyll build as I will certainly use custom plugins that are not available there.

Custom domain name

I bought at OVH, a french website hosting company.

To use a custom domain name for your blog:

  1. Create a file named CNAME at the root of your blog repository and put the url (no http://) as its content ( for my blog). You can add files directly on GitHub.
  2. Configure a new DNS entry, eg. with OVH:



It seems to be more complicated to use a root domain name (eg. for your site/blog and Github recommends to use a subdomain. I’m not very confident with my DNS configuration skills and I just ain't got time to achieve it. If you have a nice tutorial about it I may give it another try!

Remote to local

The next step is to pull your blog repo from GitHub. If you don’t know about git, you should read that. Your blog repository is divided in two branches:

  • master, that you can access at\<username>/blog. It contains the 'source code' of your blog.
  • gh-pages, available at\<username>/blog/tree/gh-pages. It contains the 'compiled HTML code' of your blog.

To pull the master branch, type the command :

git clone<username>/blog.git blog

It creates a local folder named blog that should only contain a .git directory.


This folder contains the master branch of your repository, not the gh-pages one. To simplify, the master branch contains the source code of your website, and and the gh-pages contains the generated HTML / JS / CSS files. Only the gh-pages is served as HTML pages by GitHub when you visit http://\<username> or at your custom domain name if you added the CNAME file.

To pull the gh-pages branch enter the command:

git clone -b gh-pages<username>/blog.git blog-gh-pages

It creates a local folder named blog-gh-pages that should contain the blog files generated by GitHub and your CNAME file.

Local To remote

Later on you will probably need to save your work. To do so, go into the blog folder and type:

git add -A
git commit -m “Blog import”
git push

To publish your blog, simply do the same from the blog-gh-pages folder:

git add -A
git commit -m “Welcome Jekyll”
git push

The idea is to create a Jekyll project in the blog folder, and copy the generated files in the blog-gh-pages one.


There is probably a way to use the same folder for both branches, but I like to keep things simple, especially Git!

Jekyll static website

Creating a new project is very simple. Go into the blog folder and type :

jekyll new .

To check your blog on a local web server, use the command jekyll serve and go to

Jekyll is magic


You may copy the CNAME file from blog-gh-pages to blog (Jekyll will then include it in the generated content), and clean the blog-gh-pages folder content.

To build the website run jekyll build. A _site folder is created. You just have to copy its content to blog-gh-pages to update your blog :

cp -R _site/* ../blog-gh-pages

A quicker way to do so is to build directly into blog-gh-pages:

jekyll build --destination ../blog-gh-pages/


Of course you have to push your modifications to the remote repo (on GitHub) to update your WebSite/Blog.

There is no need to commit the generated code on the master branch. So you may also add a line _site in the .gitignore file of the blog folder.

We already have a working blog now. To write more posts, you may read the manual or just try to edit files and see what's happening. When you run jekyll serve, changes are automatically visible on localhost (for all files except the _config.yml one). By the way the _config.yml holds all the configuration of your Jekyll project (If you may read only one page of Jekyll's documentation, this is the one).

This default theme is fine, but I want to add to it Bootstrap4, jQuery and Font-Awesome libraries. I could download them by hand and include then in my project. But I hate to do the same thing twice, so we are going to make this fully automatic using Bower and Grunt. And even more, we are going to use Grunt to automatically create a 'dist' version of the _site folder with code minimization and obfuscation.

Then I will customize the CSS theme to look like the Bootstrap4 blog one. In this section you can find more information about Jekyll syntax (loops, variables, etc.).

Bower dependencies management

To install bower globally :

npm install -g bower

Then you just need to create a bower.json file at the root of the blog using the bower init command. The most important part is the dependencies section. It must list all external libraries used by your project:

    "dependencies": {
     "bootstrap": "~4.0.0-alpha",
     "font-awesome": "~4.4.0"

You can add an entry to this list using the command bower install <dependency-name> --save.

And the command bower install downloads all your libs in the bower_components folder. No need to download them by hand. You also just have to edit your Bower configuration file to update the versions of you dependencies. I previously compared Grunt/Bower to Maven, but be aware that updating your libraries versions is not as smooth as in Java. JavaScript is a dynamic / script language, and there won't be any compiler to warn you if something is broken. You can rely on a heavy usage of unit testing to avoid being stuck with old libraries, like we did for ou load testing tool.


The bower.json file is only used for configuration purposes. So there is no need to include it in the generated site by Jekyll. To exclude it add the line exclude: [bower.json] to Jekyll configuration _config.yml.

There is also no need to commit the external libs, so you may also add the line bower_components to the .gitignore file.

Each package dependency should have its own bower.json. I.e. for Font Awesome check the '/bower_components/font-awesome/bower.json' file and its main section:

    "main": [

Here are listed the entry-point files necessary to use the package. Grunt will use it to inject the references toward these files into your HTML.

In some cases you may need to override them. To continue with the Font Awesome example, our bower.json contains the following overrides section:

    "overrides": {
        "font-awesome": {
          "main": [

Doing so we tell Grunt to point to the CSS file instead of using the LESS and SCSS.


We could also keep the SCSS file and import it in our 'css/main.scss'. And that's probably a cleaner way to do it.

Grunt builds

Bower alone is fine but not sufficient. I want to automatically inject the references to my libs into my HTML files. Grunt is the tool for this task.

To use Grunt we first need a package.json file:

  "name": "Blog",
  "version": "0.0.1-SNAPSHOT",
  "author": "Gérald Pereira",
  "dependencies": {},
  "devDependencies": {
    "grunt-contrib-clean": "~0.6.0",
    "grunt-contrib-copy": "~0.8.1",
    "grunt-contrib-concat": "~0.5.1",
    "grunt-usemin": "~3.1.1",
    "grunt-include-source": "~0.6.1",
    "grunt": "~0.4.5",
    "grunt-contrib-htmlmin": "~0.4.0",
    "grunt-wiredep": "~2.0.0",
    "grunt-contrib-uglify": "~0.9.2",
    "grunt-contrib-cssmin": "~0.13.0",
    "grunt-http-server": "~1.10.0",
    "grunt-bower-install-simple": "~1.1.4",
    "grunt-shell": "~1.1.2"

The devDependencies section references all dependencies that will be used by the build process. You can add some using the command npm install dependency-name --save-dev.

Then when we start npm install, a node_modules folder is created (it contains all our build libs) and we can start using Grunt.


We need to exclude the node_modules folder and other configuration files from Jekyll generated site. To do so just add the line exclude: [bower.json, GruntFile.js, package.json, node_modules] to the _config.yml file.

Configuration in GruntFile.js

The next step is to create a GruntFile.js, that defines the process of your build:

module.exports = function (grunt) {

   // Project configuration.
       pkg: grunt.file.readJSON('package.json'),

       // To download bower dependencies in bower_components folder
       'bower-install-simple': {
           options: {
               color: false,
               production: false,
               directory: 'bower_components'
       // To include js/*.js in _includes/foot.html file
      includeSource: {
          options: {               
          dev: {
              files: {
                  '_includes/head.html': '_includes/head.html'
       // To includes bower dependencies in _includes/head.html file
           dev : {
               src : [ '_includes/head.html', '_includes/foot.html'],
               ignorePath : /\.\./,
               // ignorePath : /\.\.\//, would write 
               // dependencies without the leading /
               exclude: []


   // Default task(s).
   grunt.registerTask('build:dev', [
  • You may add the configuration for each grunt module you use in the grunt.initConfig part (here bower-install-simple, wiredep and includeSource modules),
  • You must load each module using the grunt.loadNpmTasks('grunt-module-name'); syntax.
  • Finally you can declare tasks using the line grunt.registerTask('taskName', ['module1', 'module2:profileA', 'module2:profileB']);.

When launching the command grunt taskName, Grunt executes module1, then module2 with the profileA profile, and ends with module2, with the profileB profile.

Referencing dependencies in HTML

To inject references to external CSS and JS libraries, we created the build:dev task.

  1. This task starts bower-install-simple which does simply the equivalent for the bower install command.
  2. It then run a includeSource to include JavaScript files present in the js folder into the '_includes/foot.html' file.
  3. It ends with wiredep to include Bower dependencies (JS and CSS) into '_includes/head.html' and '_includes/foot.html' files.


IncludeSource is used to inject the references to our JavaScript files into '_includes/foot.html'. It's better to inject all JS into the footer to decrease pages loading times. And we don’t need to inject references to our CSS files as Jekyll comes with a .scss file compiler. It generates a single main.css file.

To include source files into the HTML, we must write special comments to tell this Grunt module where and what references to inject. For example the comment:

<!-- include: "type": "js", "files": "js/*.js", "baseUrl": "/" -->
<!-- /include -->

Is transformed to:

<!-- include: "type": "js", "files": "js/*.js", "baseUrl": "/" -->
<script src="/js/app.js"></script>
<script src="/js/toc.js"></script>
<!-- /include -->

This way if you create a new JS file for your blog or website, you just need to run a grunt build:dev command.


Wiredep is used to inject the bower dependencies into our HTML files. Here we inject them into '_includes/head.html' for the CSS ones and '_includes/foot.html' for the JS.

The ignorePath : /\.\./ configuration line is used to format the injected references. You may use ignorePath : /\.\.\// to write dependencies without the leading /.

Just like for InclureSource you need to write special comments into your HTML files to do the reference injection.

In the head.html:

<!-- bower:css -->
<!-- endbower -->


<!-- bower:css -->
<link rel="stylesheet" href="/bower_components/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="/bower_components/font-awesome/css/font-awesome.css" />
<!-- endbower -->

And in the foot.html:

<!-- bower:js -->
<!-- endbower -->


<!-- bower:js -->
<script src="/bower_components/jquery/dist/jquery.js"></script>
<script src="/bower_components/bootstrap/dist/js/bootstrap.js"></script>
<!-- endbower -->

This first step with Grunt is already a sweet productivity gain for my Jekyll usage. But it can do a lot more ...

Packaging distribution blog

For our load testing website we use a Jekyll plugin to minimize and obfuscate our code: jekyll-minifier.

It is really easy to put it in place, just install the gem gem install jekyll-minifier and add the following lines to your _config.yml Jekyll configuration file:

  - jekyll-sitemap
  - jekyll-minifier

      - 'app.js'
      - 'blog/feed.xml'

But this plugin is pretty long to execute (about 16 seconds to minify And when you run jekyll serve, it is processed every time you save a file. So you always have to wait that long to check your modifications in a browser. We ended commenting the plugin in the configuration, but we often forget to uncomment it before deploying ...

Here comes the build:dist task (the complete configuration is available on my blog-template GitHub repository):

grunt.registerTask('build:dist', [
  1. Running the build:dev task ensures that all dependencies are downloaded and wired.
  2. The shell:jekyll simply runs the jekyll build command to make sure the _site folder is present.
  3. clean:dist removes the content of our dist folder. The dist folder is '../blog-gh-pages'.
  4. copy:dist copies the relevant files from _site to '../blog-gh-pages' (HTML, CNAME, feed.xml, CSS and fonts). So every time we run this task the blog gh-pages branch is replaced with the new content (No more need to manually copy the files from _site to '../blog-gh-pages').
  5. From useminPrepare to usemin, it's the same module. Guess what? It's called Usemin. It does the minimization and obfuscation of the CSS and JS files. Se bellow for more information about it.
  6. htmlmin:dist compresses the HTML files.
  7. clean:tmp removes the folder .tmp generated by Usemin.
  8. And finally http-server:dist runs a local HTTP server so that we can check that the dist version of our blog is flawless.


You ust run grunt build:dist with the --force option because we write files outside of the current repository.


The Usemin Grunt plugin aggregates multiple JavaScript file into compressed, uglyfied, and fewer ones. It let's you save a lot of bandwidth and get better response times for your web pages.

To use it we must put special comments around JS references in our '_includes/foot.html' file:

<!-- build:js /js/vendor.js -->
<!-- bower:js -->
<script src="/bower_components/jquery/dist/jquery.js"></script>
<script src="/bower_components/bootstrap/dist/js/bootstrap.js"></script>
<!-- endbower -->
<!-- endbuild -->

<!-- build:js /js/app.js -->
<!-- include: "type": "js", "files": "js/*.js", "baseUrl": "/" -->
<script src="/js/app.js"></script>
<script src="/js/toc.js"></script>
<!-- /include -->
<!-- endbuild -->

Becomes :

<!-- build:js /js/vendor.js -->
<!-- bower:js -->
<script src="/js/vendor.js"></script>
<!-- endbower -->
<!-- endbuild -->

<!-- build:js /js/app.js -->
<!-- include: "type": "js", "files": "js/*.js", "baseUrl": "/" -->
<script src="/js/app.js"></script>
<!-- /include -->
<!-- endbuild -->

It can also do the same for CSS files :

<!-- build:css /css/vendor.css -->
<!-- bower:css -->
<link rel="stylesheet" href="/bower_components/font-awesome/css/font-awesome.css" />
<!-- endbower -->
<!-- endbuild -->


The concat:generated, cssmin:generated, uglify:generated are generated by useminPrepare. We don't need to configure them.

In my opinion, this dist task still lacks one important thing: a cache buster. These modules change the name of the vendor.js or app.js files each time you build. So when you update your blog you are sure the browser of your visitors will re-download them. It prevents your users from JS errors or CSS glitches. Usemin comes with filerev and there's also cacheBust. I may find the time to add them soon.

Whoa! That was intense! That's a lot a configuration to do. I'm not a bower/grunt master so it's a bit tedious to put all of this in place.

Grunt Mind Blown

But now comes the funny part: switching from the base Jekyll theme to the Bootstrap4 blog one.

Bootstrap 4

I really appreciate Bootstrap and I wanted to try the last version as soon as it was released. We already have Bootstrap, jQuery and FontAwesome available thanks to Bower and Grunt. And there is a clean blog theme on the getbootstrap site.

So I downloaded it, copied the CSS file as '_scss/_blog.scss' (you need to include it in the 'css/main.scss' file) and used the provided index.html as my default template.

This chapter takes a quick look at the migration from the base Jekyll theme to the Bootstrap 4 one. Topics covered here should be sufficient to understand the whole refactor. So there is no need to detail every modification.

Header.html for the navigation

The bootstrap sample index.html file must be split to fit into Jekyll. The most interesting part is the header menu (the '_includes/header.html' not to be confused with '_includes/head.html' that contains the page title, metas, and the css references).

The bootstrap theme comes with a static header:

<div class="blog-masthead">
 <div class="container">
   <nav class="nav blog-nav">
     <a class="nav-link active" href="#">Home</a>
     <a class="nav-link" href="#">New features</a>
     <a class="nav-link" href="#">Press</a>
     <a class="nav-link" href="#">New hires</a>
     <a class="nav-link" href="#">About</a>

That we transform to this:

{% assign pageURL = page.url | downcase | split: '/' %}
<div class="blog-masthead">
  <div class="container">
    <nav class="nav blog-nav">
      <a class="nav-link {% if pageURL contains 'index.html' %}active{% endif %}" href="{{ site.baseurl }}/">{{ site.title }}</a>
      {% for page in site.pages %}
      {% if page.title %}
      {% assign menuURL = page.url | downcase | split: '/' %}
      <a class="nav-link {% if pageURL == menuURL %}active{% endif %}" href="{{ page.url | prepend: site.baseurl }}">{{ page.title }}</a>
      {% endif %}
      {% endfor %}

We add the homepage link and them loop over all the available pages to add a new link into the header menu: {% for page in site.pages %} ... {% endfor %}.

We also have to check if the current link points to the current page to add the active class (It displays a tiny white triangle under the current menu): {% if currentURL == pageURL %}active{% endif %}. The {% assign currentURL = page.url | downcase | split: '/' %} syntax is used to declare variables. Here pageURL is the current page URL and and menuURL is the current menu URL.

Blog Post layout

The next step is to create the blog post layout '_layouts/post.html'.

<div class="col-sm-4 col-sm-push-8 blog-sidebar">
   <div class="sidebar-module sidebar-module-inset">
       {{ page.description }}
   <div class="sidebar-module">
       <ul id="toc"></ul>

<div class="col-sm-8 col-sm-pull-4 blog-main">
   <div class="blog-post">
       <h1 class="blog-post-title">{{ page.title }}</h1>

       <p class="blog-post-meta">{{ | date: "%b %-d, %Y" }}{% if %} • {{ }}{% endif %}{% if page.meta %} • {{ page.meta }}{% endif %}</p>

       {{ content }}

We have a right sidebar that contains the post description (the About part) followed by a table of contents (TOC). So this sidebar must come on top of the post content when the frame size is reduced.

Bootstrap is very convenient for this. We place the HTML of the sidebar before the one of the blog post content, and push it on the right for screen sizes SM and above (using col-sm-push-8 on the sidebar and col-sm-pull-4 on the content).


It could be nice to have the TOC with a fixed top position. It would then be always visible, even when the visitor scrolls down on a long article such as this one!

Every post markdown file processed by Jekyll must start with ---. For example, this post .markdown file starts with:

layout: post
title:  "How can you tell that a web developer is working?"
date:   2015-08-26 11:24:03
description: This blog's creation guide, from <strong>Jekyll</strong> base theme to <strong>Boostrap4</strong>. Using <strong>Grunt/Bower</strong> and hosted on <strong>GitHub pages</strong>. 
categories: jekyll bower grunt 
tags: bootstrap4 github
image: bootstrap4-blog-github-pages/grunt.jpg

# You can hear him **Grunt**ing!
Notice that the HTML code above references the post data using the syntax {{ }}. It lets us inject the title, name and description.

Also, everything that comes after the second --- is the content of the post. It is referenced in the HTML using the {{ content }} syntax.


Post markdown files are located in the '_posts' folder.

Table Of Contents using jQuery

The table of contents is generated using jQuery. A simpler way to generate a TOC is to use the kramdown {:toc} marker. But this solution works only with a TOC placed on top of the article content.

I started from a tutorial to display anchors on headers and adapted it to also create my TOC.

It is all in the 'js/toc.js' file:

$(function () {
   var toc = $("#toc");

   return $("h2, h3").each(function (i, el) {
       var $el, icon, id;
       $el = $(el);
       id = $el.attr('id');
       icon = '<i class="fa fa-link"></i>';
       if (id) {
           var isH2 = $el.prop("tagName") === 'H2';
           var tocLink = isH2 ? "toc-link-h2" : "toc-link-h3";
           var li = $("<li />").html($("<a />").addClass(tocLink).attr("href", "#" + id).html($el.text()));
           var html = isH2 ? li : $("<ul />").html(li);
           return $el.prepend($("<a />").addClass("header-link").attr("href", "#" + id).html(icon));

This code snippet parses all the H2 and H3 headers of the page (H1 is the page title) and adds a link to the table of content for each one. This is basic jQuery code. The only difficulty is to check for the header type (H2 or H3) to display them hierarchically.

SCSS Integration

You may have noticed that this blog doesn't exactly look like the Bootstrap theme. No?

In fact I wanted something more personal, so I made slight modifications (on fonts mostly).


SCSS lets us use variables, import external files, extend CSS classes, and much more. It's really a must have. I switched from LESS With bootstrap 3 to SASS with bootstrap 4, and the syntaxical differences are so minor that it does not even feel like a change.

First to include the Bootstrap SCSS in our own we must add the line @import "../bower_components/bootstrap/scss/bootstrap"; to our 'css/_main.scss'. The generated main.css file then includes both ours and bootstraps css classes.


The manual addition of the reference to Bootstrap 4 SCSS can probably be automated using the proper Grunt module. I'll give it a try soon.

Once Bootstrap is included we just have to import our own '_scss/_variables.scss' before it to override any variable:

@import "variables";
@import "../bower_components/bootstrap/scss/bootstrap";
@import "common";

For example, to use a black background and a blue font color, we override the corresponding variables in _variables.scss:

$body-bg: black;
$body-color: blue;

We can use an other font the same way:

@import url(;
$font-family-sans-serif: 'Share Tech Mono', monospace !default;


To know all available Bootstrap variables, take a look at the '/bower_components/bootstrap/scss/_variables.scss' file.


Well, I'm rather satisfied with the Jekyll/Grunt/Bower mix. It's not that simple to put in place. Especially because the grunt ecosystem lacks some consistency. But once done it's really a big improvement for my productivity.

There should be more posts about it coming soon:

  • A more in-depth usage of Boostrap 4,
  • SCSS imports using grunt,
  • Sitemap file generation,
  • Disqus integration,
  • Google analytics integration,
  • etc.

So stay tuned!

Want to become a super load tester?
Request a Demo