Jekyll on Github Pages with Plugins
Sarah Cassady | 17 Jul 2015 | Tags: jekyll, plugins, git, ruby, rakeJekyll is a sneaky blogging framework that tricks your brain into thinking you’re programming and not engaging in this new age-y practice of creative writing. This is great for devs like me, who are more comfortable producing readable code and well-structured documentation. What I’m doing here is ‘documenting’, not ‘writing’; ‘verbosely reviewing’, not ‘storytelling’. Whatever you think I’m doing on an emotional level, I’m not. You can’t let my brain know what’s really going on here.
Jekyll plugins
Because Jekyll runs on Ruby, you can create plugins which work much like helper methods in Rails. The main difference is that Rails helpers run on the server and respond in real-time, whereas Jekyll plugins are only used while compiling the static site files and not when they are served.
If you’re not using plugins or want to stop plugins from participating in site compilation, set value safe: true
in your _config.yml
.
However, if you are deploying your Jekyll site on Github Pages, this value is hard-coded for you.
# _config.yml
# Github Pages overrides these values
safe: true
lsi: false
source: [your top-level directory]
Deploying via Github Pages
Github Pages provides free hosting of static sites to Github users, and because Github Pages are powered by Jekyll, you can push your entire Jekyll project to your Github Pages repository and it will get built out automatically. Win-win: your site is in a git repo and deployment is already done. Too bad all your plugin awesomeness got totally ignored.
Not a huge problem; plugins don’t run on the server anyway, so we can compile the site locally before we give them to Github Pages to serve.
There are two main options you could consider. A bad option and a good option.
- The bad option: Build your Jekyll site locally and simply sync your repo with your project’s compiled site folder. It will get served like any other pile of non-Jekyl-generated flat files. Who needs to keep their project source in source control anyway?
- The good option: Fight Github with Github. When your site is served from your Github Pages repo, it is specifically being served from the master branch. With a small bit of setup, we can maintain our project source in its own branch while the master branch gets updated with sanitized versions of the compiled Jekyll site. Additionally, pushing source commits to the new branch effectively decouples our source control and ‘production’ environments, and we now have full control over production deployments.
Fighting Github with Github
If you haven’t already, add the Jekyll destination folder (default is _site
) to your .gitignore
, and delete it from the repository.
# .gitignore
# Ignore Jekyll destination folder
/_site
Create the new git branch for our source control. I’ve called mine source
.
$ git checkout -b source
Then push it back to the repo.
$ git push origin source
Your master branch and source branch should look identical.
Now delete all the files and folders in the root of the master branch.
$ git checkout master
$ git rm -rf *
$ git add -A
$ git commit -m "Clear root for static site deployment"
$ git push origin master
$ git checkout source
Next we add a task to our Rakefile
to automate deployments.
# Rakefile
namespace :git do
SOURCE_BRANCH = "source"
DEPLOY_BRANCH = "master"
DESTINATION_FOLDER = "_site"
def git_source_branch?
git_head = `git rev-parse --abbrev-ref HEAD`
source = (git_head.strip == SOURCE_BRANCH)
end
def git_clean?
git_state = `git status 2> /dev/null | tail -n1`
clean = (git_state =~ /working directory clean/)
end
desc "Verify source branch"
task :check_branch do
unless git_source_branch?
puts "Deploy not initiated from source branch. You should add `exclude: [Rakefile]` in _config.yml."
puts "Checkout #{SOURCE_BRANCH} and deploy again."
exit 1
end
end
desc "Verify clean git state"
task :check_git do
unless git_clean?
puts "Uncommitted changes. Commit or discard your changes and run deploy again."
exit 1
end
end
desc "Deploy to remote origin"
task :deploy => [:check_branch, :check_git] do
puts "Building Jekyll site"
system "jekyll build"
system "git checkout #{DEPLOY_BRANCH}"
puts "Copying #{DESTINATION_FOLDER} to root"
system "cp -r #{DESTINATION_FOLDER}/* . && rm -rf #{DESTINATION_FOLDER}"
puts "Adding .nojekyll to root"
system "touch .nojekyll"
unless git_clean?
puts "Pushing to #{DEPLOY_BRANCH}."
system "git add -A && git commit -m \"Site updated at #{Time.now.utc}\""
system "git push origin #{DEPLOY_BRANCH}"
else
puts "No changes found. Deploy aborted."
end
system "git checkout #{SOURCE_BRANCH}"
end
end
These are the steps git:deploy
takes:
- Verifies that the source branch is checked out, and no changes need to be committed
- Builds the site locally
- Switches to master branch
- Copies the compiled site into the root and deletes the destination folder
- Adds an empty
.nojekyll
file at root to opt out of Jekyll processing - Commits the new site files and pushes to master
- Switches back to source branch
Before you deploy, exclude unnecessary files in the build to sanitize the compiled site by setting or appending value exclude: []
in _config.yml
.
# _config.yml
exclude: [Gemfile, Rakefile, README]
That’s the end of the setup. Commit your source changes and deploy.
$ rake git:deploy