This blog post is a tutorial to setup a jekyll site with bootstrap installed thanks to bower, and fully hosted on github pages.
Jekyll support on github pages is great, but there are some limitations that prevent me from using it:
- I want to use bower to manage dependencies, and without pushing the bower components directory to git
- I want to be able to use custom jekyll plugins
So I prefer to directly push the generated site on github pages.
Code for this tutorial is available here: https://github.com/aymerick/jekyll-example
Result website is here: http://jekyll-example.aymerick.com
Websites I built with that setup:
- http://www.aymerick.com (source: https://github.com/aymerick/aymerick.github.io)
- http://www.williams-bretagne.org (source: https://github.com/aymerick/williams-bretagne)
Setup the master branch
WARNING: This tutorial covers the setup of a Project Page, and as so the master
branch is used as the source one, and the gh-pages
branch holds the generated site. However, if you setup a User Page then the master
branch holds the generated site, and so you have to create a new branch (eg: source
) to hold your site source.
First, create an empty jekyll-example
repository on github via the web interface.
Then generate the website:
$ jekyll new jekyll-example
Init git:
$ cd jekyll-example
$ git init
$ git add .
$ git commit -m "Generated by Jekyll v2.1.1"
Push master
to github:
$ git remote add origin git@github.com:aymerick/jekyll-example.git
$ git push -u origin master
Setup the gh-pages branch
Create an orphan gh-pages
branch:
$ git checkout --orphan gh-pages
$ git reset .
$ rm -r *
$ rm .gitignore
$ echo 'Coming soon' > index.html
$ git add index.html
$ git commit -m "init"
That way the master
and the gh-pages
branches are totally independant ones, they share no history at all.
Push gh-pages
to github:
$ git push -u origin gh-pages
Now you should see Coming soon
message when browsing to: http://aymerick.github.io/jekyll-example/
Checkout the gh-pages branch in _dist directory
Let's work in master
branch:
$ git checkout master
Checkout the gh-pages
branch into _site
directory:
$ git clone git@github.com:aymerick/jekyll-example.git -b gh-pages _site
The _site
directory is already in .gitignore
so we are just fine.
Let's test our new website:
$ jekyll serve
Browse to http://127.0.0.1:4000/ and Bingo!
Push the generated site on github:
$ cd _site
$ git add .
$ git commit -m "First generation"
$ git push
Now browse to http://aymerick.github.io/jekyll-example/ and ... wow... the jekyll site is displayed but the CSS is broken. Why ? Because you have to set the baseurl
setting in the main config file.
So edit the _config.yml
file and set:
baseurl: "/jekyll-example"
url: "http://aymerick.github.io"
Regenerate the site, and push result on github:
$ cd ..
$ jekyll build
$ cd _site
$ git add .
$ git commit -m "Fixes baseurl"
$ git push
Now browse to http://aymerick.github.io/jekyll-example/ and everything should be fine.
Let's go back locally:
$ cd ..
$ jekyll serve
Note that the local site is now accessible at http://127.0.0.1:4000/jekyll-example/
Disable jekyll build on github
To be sure that jekyll generation is not triggered on github, you must add an empty .nojekyll
file.
Then edit the _config.yml
file and set:
include:
- .nojekyll
Setup bower
Install bower:
$ npm install -g bower
$ bower init
The bower.json
file should have been created with something like that:
{
"name": "jekyll-example",
"version": "0.0.0",
"homepage": "https://github.com/aymerick/jekyll-example",
"authors": [
"Aymerick Jéhanne"
],
"description": "Jekyll setup example",
"license": "MIT",
"private": true,
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
]
}
Now, we need to exclude bower files from jekyll build. Edit the _config.yml
file and adds:
exclude:
- "bower_components"
- "bower.json"
Make git ignore bower components by adding that line to .gitignore
file:
/bower_components/
Install bootstrap
Let's install bootstrap with bower:
$ bower install bootstrap --save
Now bootstrap
and jquery
files are available here:
/bower_components/bootstrap/dist/css/bootstrap.min.css
/bower_components/bootstrap/dist/css/bootstrap.min.js
/bower_components/jquery/dist/css/jquery.min.js
I really don't want to push the whole bower_components
directory to github. So let's setup a build system with grunt that will replace jekyll
tool and that will copy the vendor files as I want it.
Setup npm
Init npm:
$ npm init
The package.json
file should have been created with something like that:
{
"name": "jekyll-example",
"version": "0.0.0",
"description": "Jekyll setup example",
"main": "_site/index.html",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git://github.com/aymerick/jekyll-example.git"
},
"author": "Aymerick Jéhanne",
"license": "MIT",
"bugs": {
"url": "https://github.com/aymerick/jekyll-example/issues"
},
"homepage": "https://github.com/aymerick/jekyll-example"
}
To exclude npm files from jekyll build, edit the _config.yml
file and adds:
exclude:
- "node_modules"
- "package.json"
- ...
Make git ignore npm packages by adding that line to .gitignore
file:
/node_modules/
...
The grunt file
First, install needed grunt packages:
$ npm install -g grunt-cli
$ npm install grunt grunt-bower-task grunt-contrib-connect grunt-contrib-copy grunt-contrib-watch grunt-exec --save-dev
Now the fun part begins, let's create the grunt file. I'm not a big fan of coffescript, but for the purpose of a makefile I think its syntax is elegant and cleaner than raw javascript.
So here is the Gruntfile.coffee
file:
#global module:false
"use strict"
module.exports = (grunt) ->
grunt.loadNpmTasks "grunt-bower-task"
grunt.loadNpmTasks "grunt-contrib-connect"
grunt.loadNpmTasks "grunt-contrib-copy"
grunt.loadNpmTasks "grunt-contrib-watch"
grunt.loadNpmTasks "grunt-exec"
grunt.initConfig
copy:
jquery:
files: [{
expand: true
cwd: "bower_components/jquery/dist/"
src: "jquery.min.js"
dest: "vendor/js/"
}]
bootstrap:
files: [{
expand: true
cwd: "bower_components/bootstrap/dist/css/"
src: "bootstrap.min.css"
dest: "vendor/css/"
},
{
expand: true
cwd: "bower_components/bootstrap/dist/js/"
src: "bootstrap.min.js"
dest: "vendor/js/"
}]
exec:
jekyll:
cmd: "jekyll build --trace"
watch:
options:
livereload: true
source:
files: [
"_drafts/**/*"
"_includes/**/*"
"_layouts/**/*"
"_posts/**/*"
"css/**/*"
"js/**/*"
"_config.yml"
"*.html"
"*.md"
]
tasks: [
"exec:jekyll"
]
connect:
server:
options:
port: 4000
base: '_site'
livereload: true
grunt.registerTask "build", [
"copy"
"exec:jekyll"
]
grunt.registerTask "serve", [
"build"
"connect:server"
"watch"
]
grunt.registerTask "default", [
"serve"
]
First let's exclude the grunt file from jekyll build, by editing the _config.yml
file and adding:
exclude:
- "Gruntfile.coffee"
- ...
The grunt tasks
The copy
subtask copies the jquery
and bootstrap
files to a new vendor
directory. That directory will be copied as is by jekyll to _dist
so it acts as a temporary zone and must be ignored by git. Adds that line to .gitignore
file:
/vendor/
...
The exec:jekyll
subtask invokes the jekyll
tool to build the site into _site
directory.
The connect:server
subtask launches a server on port 4000
.
The watch
subtask rebuilds the site when a source file changes. The list of all watched source files must be provided in the files
setting. Remember to update that setting when you add custom directories or files.
Note the use of livereload: true
in both watch
and connect:server
subtasks. Thanks to that setting, your browser will reload automatically when one of the source files changes.
Include bootstrap files
Let's include vendor
files in our jekyll site:
Edit the _includes/head.html
file and add that line:
<!-- Custom CSS -->
<link rel="stylesheet" href="/vendor/css/bootstrap.min.css">
Edit the _layouts/default.html
file and add those lines before </body>
:
<script src="/vendor/js/jquery.min.js"></script>
<script src="/vendor/js/bootstrap.min.js"></script>
The baseurl dilemma
So now, you can replace the jekyll serve
command with:
$ grunt
Or if you want verbose logs:
$ grunt -v
But there is a problem:
- If you browse to http://127.0.0.1:4000/jekyll-example you get a
Cannot GET /jekyll-example/
error - If you browse to http://127.0.0.1:4000/ then the CSS is broken
That's because jekyll is configured with baseurl: "/jekyll-example"
so that it is browsable at http://aymerick.github.io/jekyll-example/ but there is no such setting with grunt-contrib-connect
. The grunt-contrib-connect
package expects you to browse http://127.0.0.1:4000/.
Custom domain name
I personnally always host my jekyll sites on custom domain names, so I will host that example on http://jekyll-example.aymerick.com instead of http://aymerick.github.io/jekyll-example/.
That way I don't need a baseurl
setting and this setup works the same for development and for production. If you really need to keep the baseurl
setting then you should probably create a new 'production specific' grunt task that will build jekyll with the --baseurl URL
option.
So, first you need to comment the baseurl
setting in the _config.yml
file:
# baseurl: "/jekyll-example"
Then create a CNAME
file with the custom domain name:
jekyll-example.aymerick.com
Finally go to your DNS provider website and setup a CNAME
from jekyll-example.aymerick.com
to aymerick.github.io
.
Let's push everything:
$ grunt build
$ cd _site
$ git add .
$ git commit -m "Setup bootstrap and custom domain"
$ git push
$ cd ..
And now, browse to http://jekyll-example.aymerick.com.
You should probably see a 404 There isn't a GitHub Page here.
error, that's because it takes some time for github to setup your new custom domain configuration. Just wait a few minutes then try again, you will eventually see your site.
The deploy task
It is tedious to manually commit and push the gh-pages
branch when you want to deploy to github pages. Let's create a rake task that will help us with that.
Create a Rakefile
file:
require "rubygems"
desc "Deploy to Github Pages"
task :deploy do
puts "## Deploying to Github Pages"
puts "## Generating site"
system "grunt build"
cd "_site" do
system "git add -A"
message = "Site updated at #{Time.now.utc}"
puts "## Commiting: #{message}"
system "git commit -m \"#{message}\""
puts "## Pushing generated site"
system "git push"
puts "## Deploy Complete!"
end
end
Exclude Rakefile
from jekyll build, by editing the _config.yml
file and adding:
exclude:
- "Rakefile"
- ...
And now, deploying your site on github page is as simple as running:
$ rake deploy
Conclusion
With that setup, you only have two commands to run:
grunt
while developing, with live reload supportrake deploy
to deploy the site in production
You can now add more bower packages, use custom jekyll plugins, and have fun :)
(You can contact me @aymerick and I want to thanks @octplane for reviewing this tutorial)