I recently migrated from Octopress to Jekyll after I’ve figured out how to build a vanilla Jekyll project. In this tutorial, you’re going to learn how to start a new project and manage the dependencies correctly.

Why Another Jekyll Tutorial?

Most of the online tutorials don’t address my main concern with learning a new framework.

How do I build a Jekyll site without a built-in theme and dependencies? I want to make everything from scratch.

You’re likely to find this tutorial useful if you fit into the following criteria:

  • Comfortable developing a website with only HTML, CSS, and JavaScript.
  • Use version control like Git to track and deploy your projects.
  • Understand the advantage of using Homebrew to manage packages.
  • Prefer to use version managers like rbenv, pipenv, and nvm to avoid running sudo when installing Ruby, Python, or Node on your working machine.

There is a quick way to start a Jekyll site, but we’re going to choose the best way by starting the project with Bundler.

Bundler provides a consistent environment for Ruby projects by tracking and installing the exact gems and versions that are needed.

With Bundler, you can clone your repository and get the Jekyll site running in minutes. Read more about installing Jekyll with Bundler. Run the command lines below to create a my-jekyll-website folder and set up a bundle.

mkdir my-jekyll-website
cd my-jekyll-website
bundle init

This is optional, but I prefer to install the bundle in the project subdirectory to avoid permission errors you might get during the gem installation.

bundle config set --local path 'vendor/bundle'

Instead of running bundle add jekyll to add Jekyll as a dependency for your new project, you want add this line item gem "jekyll", github: "jekyll/jekyll" to the Gemfile.1 Replace the content of Gemfile with the following snippet.

source "https://rubygems.org"
gem "jekyll", github: "jekyll/jekyll"

A Gemfile is a file we create which is used for describing gem dependencies for Ruby programs. A gem is a collection of Ruby code that we can extract into a “collection” which we can call later. — Tosbourn

Install the gems required to run Jekyll with the command below. Wait until the installation process is complete.

bundle install

Run the following command to create a new Jekyll folder called source that contains your source code. We need to add --blank option to create a blank Jekyll directory structure to help you understand the purpose of each file and folder.

bundle exec jekyll new source --blank

Move _config.yml from the source directory to the root directory by running this command:

mv source/_config.yml .

Here’s the directory structure of your Jekyll project.

my-jekyll-website/
  | source/
  | vendor/
  | _config.yml
  | Gemfile
  | Gemfile.lock

Execute the following command to build and serve the site locally.

bundle exec jekyll serve

You will see this build warning in the console.

Build Warning: Layout 'default' requested in source/index.md does not exist.

The first line mentioned that the homepage index.md is using the default layout that doesn’t exist. This is happening because _config.yml tried to build the site using the folders from the same directory, which happened to be in the source folder. We can fix this error by updating config.yml.

url: "" # the base hostname & protocol for your site, e.g. http://example.com
baseurl: "" # the subpath of your site, e.g. /blog
title: "" # the name of your site, e.g. ACME Corp.

# Set the source folder
source: source

Rebuild the site with this command that tells Jekyll to incrementally update the site whenever it detects change with any of the files.

bundle exec jekyll serve --incremental

You should be able to view the site by visiting http://127.0.0.1:4000/. Congratulations! You’ve learned how to set up and build a Jekyll site.

Development with Jekyll

This is the most exciting and time-consuming step in the whole process: developing your Jekyll website from scratch.

Jekyll is a static site generator, so you can create index.html in the source directory to build a single-page website.

But, we’re not going to settle with a single page site.

We’re building a site that comes with a blog, a site that can have many posts and pages, and a site where you can experiment with a different type of layouts.

Setting Up Git

Before we start making changes to the project, we want to use Git to track any updates with our files. Go ahead and run git init to track your project while you’re in the console. You also want to add these lines to .gitignore file found in the folder.

# Jekyll
.bundle
.jekyll-cache/
.jekyll-metadata
.sass-cache/
_site/
Gemfile.lock
vendor/

You can also add these lines above into your global .gitignore that can be found in ~/.gitignore. It’s useful to add them as a global setting if you find yourself developing many Jekyll projects in the future.

Jekyll Directory Structure

Jekyll is the most elegant static site generator I’ve used. It’s the only framework where I only have to deal with HTML, CSS, and Markdown files.

Each folder created in the source directory has a purpose. I recommend that you read the explanation of the directory structure before you continue with this tutorial.

The first concept we need to remember is any post or page in Markdown format can be rendered as a special file only when we’ve added a valid front matter at the beginning of the document. I’ve spent many hours unable to figure out why some pages don’t show up correctly because I forgot about this concept.

Front Matter — it must be the first thing in the file and must take the form of valid YAML set between triple-dashed lines.

Open index.md inside source directory to see its content. You will find that layout variable is set inside the front matter.

---
layout: default
---

It tells Jekyll to use the default.html found inside _layouts folder as its parent layout. If you modify anything in default.html, it will also be rendered for any page that uses default.html as the layout.

You’re probably wondering how index.md content shows up in default.html. Let’s learn more about it by checking out the content:


<!DOCTYPE html>
<html lang="{{ site.lang | default: "en-US" }}">
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta charset="utf-8">
    <title>{{ page.title }} - {{ site.title }}</title>
    <link rel="stylesheet" href="{{ "/assets/css/main.css" | relative_url }}">
  </head>
  <body>
    {{ content}}
  </body>
</html>

The {{ content }} tag tells Jekyll to load the content of a post or page that uses this layout.

You must remember that _layouts folder is your building blocks to design a website. The only time you want to modify anything inside this folder is whenever you want to change your site HTML structure. You never have to write the content directly here.

Typically, you want to use default.html as the first building block for other layouts you’re going to create. Here’s a common way to structure the _default.html.


<!DOCTYPE html>
<html lang='en'>
{% include head.html %}
<body>
  <div class="container">
    {% include header.html %}
    {{ content }}
    {% include sidebar.html %}
    {% include footer.html %}
  </div>
</body>
</html>

Notice that we’re using {% include <filename.ext> %} tag several times. It tells Jekyll to include the corresponding files that are found inside _includes folder.

You can treat anything inside _includes folder as components for you to use in your layout or post. You can also create more folders there to further organize your components.

Here is what I have in the folder when this post is published. You’re going to create additional components as you build a more complex layout, or by adding various sections on your site.

_includes/
  | custom/
    | extra.html
    | opengraph.html
    | twitter_card.html
  | footer.html
  | head.html
  | header.html
  | sidebar.html

Organize Pages with Collections

We’re halfway into this tutorial, but we haven’t discussed anything about the posts folder.

Let’s take a break.

Good?

You want to make your website looks alive with many pages. You don’t want to have a homepage full of blog posts without other pages to feature a different type of content.

There are two ways to create a new page.

First, you can add more pages by creating a new folder named after the permalink you want to use, and put an index.md or index.html in the folder. For example, here is how the source directory looks like if you want to create an about page with this method.

source/
  | _data/
  | _drafts/
  | _includes/
  | _layouts/
  | _posts/
  | _sass/
  | about/
    | index.html
  | assets/
  | posts/
  | index.html

This is the easiest way to create a new page, but you may find that your source folder cluttered with folders if you’re creating many pages.

There is a better way to create pages in Jekyll. We’re going to use collections.

Start by creating a new folder called _pages on the same directory level as others. Create a new file called about.html and put it inside _pages folder.

---
layout: default
title: About
permalink: /about/
---
Tell people about yourself.

You will find a new variable called permalink inside the front matter. You can change it into permalink: /me/ if you want the about page accessible from visiting http://127.0.0.1:4000/me/

The pages won’t be accessible yet because there is one more step left. We’ve to set up the collections by adding these lines to _config.yml.

# Jekyll Collections
collections:
  pages:
    output: true # Set to render files in _pages folder
    permalink: /:title/ # Set the format of the permalink by default 

Visit http://127.0.0.1:4000/about/ again should load the page.

With the _pages folder ready, you can add more pages by creating html or markdown files in the folder. The structure remains organized even after we’ve added a contact and portfolio page.

source/
  | _data/
  | _drafts/
  | _includes/
  | _layouts/
  | _posts/
  | _pages/
    | about.html
    | contact.html
    | portfolio.html
  | _sass/
  | assets/
  | posts/
  | index.html

Pagination and Iteration with Posts

If you’ve published any posts from your previous setup, you can copy markdown files into the new project posts folder. We’re going to use actual content to start building the posts page.

Pagination is the most important feature on the posts page because we want to avoid displaying all the posts on a single page. Instead of using jekyll-paginate recommended by Jekyll, we’re going to use jekyll-paginate-v2 to create a paginated posts page.

Open Gemfile and update its content into:

source 'https://rubygems.org'
gem "jekyll", github: "jekyll/jekyll"
group :jekyll_plugins do
  gem 'jekyll-paginate-v2'
end

There are three ways to install plugins, but I recommend using Gemfile because we started with Bundler to manage the dependencies.

To enable pagination, you need to configure the pagination settings in _config.yml:

url: "" # the base hostname & protocol for your site, e.g. http://example.com
baseurl: "" # the subpath of your site, e.g. /blog
title: "" # the name of your site, e.g. ACME Corp.
source: source

# Pagination
pagination:
  enabled: true # must be set to true
  per_page: 10 # number of posts displayed per page
  permalink: '/page/:num/' # the pagination format
  sort_reverse: true # show the latest post first

# Collections
collections:
  pages:
    output: true # Set to render files in _pages folder
    permalink: /:title/ # Set the format of the permalink by default 

You can now access paginator liquid object to iterate every post as long you’ve enabled it in the front matter.

Depending on where you want to display your latest posts, you will have a different directory structure. If you’re going to show the latest posts and pagination on the homepage, you can rename index.md into index.html and update the content with this:


---
layout: default
pagination: 
  enabled: true # Enable pagination on this page
---

<!-- This loops through the paginated posts -->
{% for post in paginator.posts %}
  <h1><a href="{{ post.url }}">{{ post.title }}</a></h1>
  <span class="date">{{ post.date }}</span>
  <div class="content">
    {{ post.content }}
  </div>
{% endfor %}

<!-- Pagination links -->
<div class="pagination">
  {% if paginator.previous_page %}
    <a href="{{ paginator.previous_page_path }}" class="previous">
      Previous
    </a>
  {% endif %}
  {% if paginator.next_page %}
    <a href="{{ paginator.next_page_path }}" class="next">Next</a>
  {% endif %}
</div>

Your site now supports the basic features of a blogging platform. You can create more pages by creating files in _pages folder. Here are more tips and resources to help you get started developing your site:

Deployment to GitHub Pages

Check out how to configure custom domain on GitHub Pages if you haven’t done so. If you’ve already published your site on GitHub, you want to clone the repository to your desktop by using this command:

git clone <your_github_repo_url> ~/Desktop/_site

Replace your Jekyll _site folder with the cloned _site folder from GitHub. You should see that the folder contains files like CNAME and .git. To prevent Jekyll from removing them whenever we build the site, we need to exclude them by adding this line into _config.yml

keep_files: [CNAME, .git]

To build for the production, you can enter this command:

JEKYLL_ENV=production bundle exec jekyll build

It will override the content of _site folder with the new one. Once you’re satisfied with your site, you can push the commit back to GitHub to make the new site live.

Conclusion

Jekyll is the first framework I can use to swiftly build and deploy a static site from scratch. It provides me with the environment to practice the fundamental of building a semantic and responsive site with HTML/CSS.

I know there are people like me who prefer to keep the website development process simple: a plain old HTML, CSS, and maybe some JavaScript to spice up the site.

  1. There is a bug with the version of Jekyll that is hosted on RubyGems.org. That’s why you want to use the latest stable release from the repository directly.