Fully Functional Jekyll Blog

Share this article

Blog (блог). Красное слово на белом фоне

There’s comes a time when tutorials on getting started aren’t enough. This is not one of those tutorials. After this article, you will have the ability to make a fully functional Jekyll blog, complete with pagination and search capabilities. Get ready to see just how awesome Jekyll can be.


This is for:

  • People who have basic knowledge of Jekyll (Note: If you don’t read the docs. It’s easy to get to basic.)
  • Know how to use a text editor
  • Can do gem install
  • Are very good at HTML and JavaScript
  • Understand Liquid templating (Again…see the docs if not)


If at any point you are lost or confused, these links should help you out:


To make sure we’re all on the same page, I am going to be using Jekyll version 2.5.3. So, if you haven’t yet, you should gem install jekyll -v 2.5.3.

We will not be using the jekyll new command to create this blog. It’s great for people who want to get the feel of a Jekyll blog, but I feel that it takes away from the learning experience. Instead, let’s start off by creating a fresh git repository and adding a config.yml file to it. This config.yml is where all of the site’s configuration goes. Variables in this file can be accessed with the global site variable (e.g. site.foo).

$ mkdir jekyll-blog
$ cd jekyll-blog
$ git init
$ git checkout -b gh-pages # for GitHub pages support
$ echo "2.2.2"; > .ruby-version # sets Ruby version
$ touch _config.yml

The YAML file:

# _config.yml
title: A Sample Blog # your blog's name
author: Jesse Herrick # your name
baseurl: /blog/ # we'll get to this later
exclude: # things to exclude from the build process
  - README.md
  - .ruby-version
  - Gemfile
  - Gemfile.lock

Now, some git to make sure we start off right:

$ git add .
$ git commit -m "Initial blog configuration"
$ git push -u origin gh-pages # you should create a GitHub repo if you haven't yet

Cool stuff. Instead of our master branch being our deployment branch, gh-pages will serve that role.

Before we just jump right in, let’s look at what we want in our blog:

  • An index page of posts
  • A page for individual posts
  • The ability to search posts

We’ll need some actual posts to test with, so I generated a few using a Lorem Ipsum generator. For your convenience, you can get them here. Put these in a directory called, _posts.

Every page in Jekyll needs a default template, so make one before creating the post index page. This saves us from having to write all the obligatory stuff:

$ mkdir _layouts
$ touch _layouts/default.html

And the HTML:

# _layouts/default.html

<!DOCTYPE html>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>{% if page.title %}{{ page.title }} &&middot; {% endif %}{{ site.title }}</title>
    {% include libs.html %}
    {{ content }}

You’ll notice in the title tag that we used an if statement:

{% if page.title %}{{ page.title }} &&middot; {% endif %}

This states: if the page variable, title is defined, then render it alongside a separator. If not, then nothing will be shown.

We also used the include liquid tag to pull in an external HTML file called libs.html that lives in the _includes directory and contains our libraries. It’s possible to separate every template into smaller ones, but, at a certain point, it gets to be more time consuming than it’s worth.

# _includes/libs.html

<!-- Stylesheets -->
<link href='//fonts.googleapis.com/css?family=Arvo:400,400italic,700' rel='stylesheet' type='text/css'>
<link href="{{ '/css/style.css' | prepend: site.baseurl }}">

<!-- JS -->
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>
<script src="{{ '/js/app.js' | prepend: site.baseurl }}"></script>

Awesome. Notice that we used the liquid filter, prepend, to prepend our site’s previously defined base URL. It’s defined as /blog/ so that the assets load properly if the site is hosted at URL like http://johndoe.github.io/blog/. This is helpful because our default layout is going to be applied to all pages.

Now let’s make the index page.

The index page needs to display whole posts and paginate every 3 posts (we have 4). Also, it should have links to individual post pages. Jekyll makes this super easy.

# index.html

title: Home
layout: default

<h1>{{ site.author }}'s Blog</h1>

<section class="posts">
{% for post in site.posts %}
  <li><date>{{ post.date | date: "%B %-d, %Y"}}<a href="{{ post.url | prepend: site.baseurl }}">{{ post.title }}
{% endfor %}

It’s just a plain and simple index page with a classic for..in loop over the posts.

The AngularJS ng-repeat directive is perfect for a quick live search of posts. But first, we need some sort of API for Angular to consume. Jekyll can actually do this for us in pure liquid. Because the site is statically generated, the data in our “API” doesn’t need to be dynamically generated either. So, to generate the API, just use the following code:

# posts.json
layout: null

  "posts": [
    {% for post in site.posts %}{
      "title": "{{ post.title }}",
      "url": "{{ post.url | prepend: site.baseurl }}",
      "date": "{{ post.date | date: "%B %-d, %Y" }}",
      "raw_date": "{{ post.date }}"
    }{% unless forloop.last %},{% endunless %}
    {% endfor %}

It’s pretty simple and generates a nice API for our blog that looks something like this:

  "posts": [
      "title": "Sample 4",
      "url": "/2015/05/20/Sample-4.html",
      "date": "May 20, 2015",
      "raw_date": "2015-05-20 00:00:00 -0400"
      "title": "Sample 3",
      "url": "/2015/05/18/Sample-3.html",
      "date": "May 18, 2015",
      "raw_date": "2015-05-18 00:00:00 -0400"
      "title": "Sample 1",
      "url": "/2015/05/17/Sample-1.html",
      "date": "May 17, 2015",
      "raw_date": "2015-05-17 00:00:00 -0400"
      "title": "Sample 2",
      "url": "/2015/05/15/Sample-2.html",
      "date": "May 15, 2015",
      "raw_date": "2015-05-15 00:00:00 -0400"

So how do we use this in our blog to add search functionality?

# js/app.js

angular.module('JekyllBlog', [])
  .controller('SearchCtrl', ['$scope', '$http', function($scope, $http) {
    $http.get('/posts.json').success(function(data) {
      $scope.posts = data.posts;

Now, add this to the index page, but there’s a problem: Jekyll will parse AngularJS’ brackets.


<!-- is parsed into... -->

This is because Jekyll runs all templates through Liquid, which assumes (as it should) that all brackets are part of the Liquid templates. So, we have to get around this. One option is this plugin that I wrote, but since we plan on deploying to GitHub pages, custom plugins aren’t an option. We’ll have to use Liquid’s {% raw %} tags (trust me, not as fun).

# index.html

<div class="posts" ng-controller="SearchCtrl">
  <input type="search" class="search" ng-model="query">
    <li ng-repeat="post in posts | filter:query ">
      <date ng-bind="post.date"></date>
      <a href="{% raw %}{{ post.url }}{% endraw %}" ng-bind="post.title"></a>

The special sauce here is the combination of ng-repeat and a filter. We’re basically telling Angular to display each item in the posts array (aliased as post) that matches the query scope variable. You’ll notice that, at the top of the list we added a search input bound by ng-model to the query scope variable. Then we just reuse same output as used earlier in {% for post in posts %}, but using the Jekyll “API”.

What About People Who Don’t Have JS?

If you really need to cater to those people that don’t have JavaScript enabled, you can use a little ng-cloak magic.

# css/styles.css (as recommended by: https://docs.angularjs.org/api/ng/directive/ngCloak)
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
  display: none !important;
# index.html 

<div ng-cloak class="posts" ng-controller="SearchCtrl">
  <input type="search" class="search" ng-model="query">
    <li ng-repeat="post in posts | filter:query ">
      <date ng-bind="post.date"></date>
      <a href="{% raw %}{{ post.url }}{% endraw %}" ng-bind="post.title"></a>

<div ng-show class="posts">
    {% for post in posts %}
    <li><date>{{ post.date | date: "%B %-d, %Y"}}<a href="{{ post.url | prepend: site.baseurl }}">{{ post.title }}</a></li>
    {% endfor %}

This works through some interesting logic. First, AngularJS’ ng-cloak directive is detected by some CSS styles and is hidden until AngularJS is loaded. Thus, if JS is disabled, nothing is shown.

Next, the ng-show directive is added to the Jekyll-generated list of posts. This works because AngularJS defaults ng-show to false, meaning that the Jekyll list will only be shown if AngularJS is not loaded.

What About Posts?

Each post has a link, but we haven’t really talked about what happens when you click that link. Right now, it displays the post’s markdown rendered to HTML, but we want more. First, let’s add a default layout to all of our posts because we shouldn’t have to worry about that when writing a new post:

# _config.yml

... other config ...
      path: ""
      type: "posts"
      layout: "post"

This means that layout: post will be added to all post type files. Now we need to create this post layout:

$ touch _layouts/post.html

This layout only really needs a heading and a place to render the post:

# _layouts/post.html -->

  <h2>{{ page.title }}</h2>
  <small>Written by <strong>{{ site.author }}</strong><date>{{ page.date | date: "%B %-d, %Y" }}</date>.</small>

  {{ content }}

If you’re familiar with Rails, {{ content }} is like < %= yield %> in terms of layouts. In this case, it is yielding to content passed in each post. Notice that we also used page.title and page.date rather than post.title, etc. because we are calling a variable that is only passed to the page (layout) and not through an iteration.

Now, if you visit a post link, a nice heading and some meta info about the post on that page are displayed.


There you have it! A fully functional Jekyll blog. A little more styling and you have something to write use to your heart’s desire. If you’d like to learn more about what Jekyll can do, check out the docs. Happy Blogging!

Frequently Asked Questions about Fully Functional Jekyll Blog

How can I add a search bar to my Jekyll blog?

Adding a search bar to your Jekyll blog can enhance the user experience by making it easier for visitors to find the content they’re interested in. You can use Simple-Jekyll-Search, a lightweight plugin that provides a fully functional search bar. To implement it, you need to add the Simple-Jekyll-Search script to your site, then add a search input and results container in your HTML. You can customize the search bar’s appearance and functionality according to your needs.

How can I wrap every two posts in a div in Jekyll?

Wrapping every two posts in a div can help in organizing your blog layout. In Jekyll, you can achieve this by using a for loop in your Liquid template. You can create a counter and increment it for each post. Then, use an if statement to check if the counter is divisible by two. If it is, close the current div and open a new one. This will effectively wrap every two posts in a separate div.

How can I add multiple content variables in one layout file in Jekyll?

In Jekyll, you can add multiple content variables in one layout file by using YAML front matter. This is a set of custom variables you can define at the beginning of your markdown file. You can then access these variables in your layout file using Liquid tags. This allows you to customize the content and appearance of your blog posts based on the variables you’ve defined.

How can I add a comment section to my Jekyll blog?

Adding a comment section to your Jekyll blog can encourage interaction and engagement from your readers. You can use Disqus, a popular third-party comment hosting service. To integrate Disqus with your Jekyll blog, you need to sign up for a Disqus account, add the Disqus shortname to your _config.yml file, and include the Disqus code in your post layout file.

How can I add pagination to my Jekyll blog?

Pagination is a useful feature for blogs with a large number of posts. In Jekyll, you can add pagination by using the jekyll-paginate plugin. After installing the plugin, you need to add the pagination configuration to your _config.yml file and modify your index.html file to display the paginated posts. You can customize the number of posts per page and the appearance of the pagination links according to your needs.

How can I add categories and tags to my Jekyll blog?

Categories and tags can help your readers navigate your blog and find the content they’re interested in. In Jekyll, you can add categories and tags to your posts by defining them in the YAML front matter. You can then create pages or sections on your site to display posts by category or tag. You can also add category and tag links to your post layout to make it easier for readers to find related content.

How can I add a custom domain to my Jekyll blog?

Adding a custom domain to your Jekyll blog can make it more professional and memorable. You can do this by purchasing a domain from a domain registrar, then configuring your DNS settings to point to your GitHub Pages site. You also need to add a CNAME file to your repository with your custom domain.

How can I add a RSS feed to my Jekyll blog?

An RSS feed can help your readers stay updated with your latest posts. In Jekyll, you can add an RSS feed by creating an XML file in your site’s root directory. You can use Liquid tags to generate the feed items based on your posts. You also need to add a link to the RSS feed in your site’s header or footer.

How can I optimize my Jekyll blog for SEO?

SEO is important for increasing your blog’s visibility in search engine results. In Jekyll, you can optimize your blog for SEO by using the jekyll-seo-tag plugin. This plugin adds SEO-friendly meta tags to your site’s head. You can also improve your SEO by using descriptive titles and headings, adding alt text to images, and creating high-quality, original content.

How can I add a contact form to my Jekyll blog?

A contact form can make it easier for your readers to get in touch with you. In Jekyll, you can add a contact form by using a third-party service like Formspree or Netlify Forms. You need to create a form in your HTML, then set the form’s action attribute to the URL provided by the service. You can customize the form fields according to your needs.

Jesse HerrickJesse Herrick
View Author

Jesse Herrick is an avid Ruby developer who specializes in web development. He is a back-end developer at Littlelines and loves programming. You can read his personal blog at: https://jesse.codes.

Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week
Loading form