Create an Hexo Theme - Part 1: Index

2016-03-08

In this multi part tutorial, you will learn how to create an Hexo Theme from scratch. I really love Hexo and use it everyday; unfortunately, as of today, the documentation for theme creation is pretty slim. Here is my attempt at fixing that.

Prerequisites

Project Description

This project is all about creating a theme for Hexo and understanding in details how the Hexo engine

Since I don’t want to lose too much time on the HTML and CSS parts we are going to recreate the following theme in Hexo: http://getbootstrap.com/examples/blog/. It is one of the standard Getting Started Template Examples in the Boostrap documentation.

We are going to reuse the CSS and copy paste the HTML, piece by piece, until we have achieved what we want.

If you get lost or are only interested in the code, it is on github.

Project setup

Create a new hexo blog

Let’s get started with a brand new hexo installation.

1
2
3
4
5
6
# Create a new folder
mkdir hexo-theme-creation
cd hexo-theme-creation

# Initialise Hexo
hexo init

Create the theme folder

1
2
3
4
5
# Enter the theme folder
cd themes

# bootstrap-blog-hexo is also going to be the name of our theme
mkdir bootstrap-blog-hexo

Note: If you want to save the theme to git (as you should), initialise git inside /themes/bootstrap-blog-hexo/.

Folder structure

Here are the files and folders we will need to get started:

1
2
3
|-- layout // .ejs templates 
|-- source // source files (CSS, scripts)
|-- _config.yml

Create these 2 folders and the _config.yml file.

Copy the bootstrap blog assets over

In our source folder, copy all the assets we need from the bootstrap blog template. View source from your browser and copy everything over or download this package and extract it in your source folder.

1
2
3
4
5
6
7
8
|-- layout 
|-- source
|-- bootstrap // Copy the boostrap library files here
|-- css // Copy the blog's css file here
|-- favicon
|-- favicon.ico // Your choice of favicon
|-- js // Copy the blog's js file here
|-- _config.yml

Basics of Hexo

Before we write our first template file, let’s look at the basic of Hexo blog generation.

Page types

Corresponds to the 6 types of pages we can define in our theme, every singe HTML page generated in the public folder belongs to one of these templates:

Template Fallback Page Description
index None This the home page of the blog, the main entry point. In our case it will display a list of blog excerpts.
post index This is the detail page for posts. Here we will display only one post in full, with a comment section.
page index This is the detail page for pages. Same as post but for ‘page type’ posts.
archive index This is the archive page. It will display a list of all the posts in our blog with just titles and links to the detail page.
category archive This is the category page. Similar to the archive page but filtered for one category.
tag archive This is the tag page. Similar to the archive page page but filtered for one tag.

In this part of the tutorial we will build the index.

During the generation is Hexo will look for files named index.ejs, post.ejs, page.ejs and so on. These templates are then rendered to create the static HTML pages.

Common Layout

Hexo supports the use of a common layout file that will be used by all the templates above.

This file has to be named layout.ejs. It acts as a wrapper around the content rendered by the different page type templates above.

In our theme, the layout will contain: the <html> and <head> tags, the header and menu as well as the footer and the sidebar. Basically all the elements that are common to all pages.

The different page templates will only be responsible for creating the actual content, that will be placed inside our main container.

Variables

Inside all our templates, we have access to some variables that are injected by the hexo engine. Here are some of them:

- Site

site contains site wide information. For example, with site.posts we can access all the posts in the blog. Useful if we want to display statistics in a widget for example.

- Page

page is the main variable and contains a lot of information related to the current page, including all the post titles, dates, content and so on.

The properties of that object depend on which page template (index, post, archives) we are on. The full list is available here; but let’s look at them as we go.

- Config

config is a JavaScript object representation of the main _config.yml of the blog.

- Theme

theme is a JavaScript object representation of the theme’s _config.yml.

Theme’s layout creation

We will start by creating the /layout/layout.ejs file discussed above.

The head section

Let’s start by creating a layout.ejs file and inserting the <html></html>

layout/layout.ejs
1
2
3
4
5
6
<html>

<!-- Head tag -->
<%- partial('_partial/head') %>

</html>

Here we are extracting all the <head> code into a partial view. Partial views promote separation of concern and reusability in our code.

The syntax is partial('path' [, arguments])

After creating that layout/_partial/head.ejs file, we are going to copy the head code from the bootstrap source code:

layout/_partial/head.ejs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">

<meta name="description" content="">
<meta name="author" content="">
<link rel="icon" href="favicon/favicon.ico">

<title>Blog Template for Bootstrap</title>

<!-- Bootstrap core CSS -->
<%- css('bootstrap/css/bootstrap.min.css') %>

<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
<%- css('css/ie10-viewport-bug-workaround.css') %>

<!-- Custom styles for this template -->
<%- css('css/blog.css') %>

<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->

</head>

Pretty straightforward. All we have done is use a CSS helper to insert our style sheets.

The files in our source folder will be copied at the root of our generated site, so source/ should not be included in the paths.

We will make the <title> and meta tags dynamic down the line but let’s leave it like so for now.

The after footer section will be included just before the end of our <body> section. In that partial view, we will include all our scripts.

Let’s modify the layout:

layout/layout.ejs
1
2
3
4
5
6
7
8
9
10
11
12
<html>

<!-- Head tag -->
<%- partial('_partial/head') %>

<body>

<!-- After footer scripts -->
<%- partial('_partial/after-footer') %>
</body>

</html>

And create the content of the new layout/_partial/after-footer.ejs partial:

layout/_partial/after-footer.ejs
1
2
3
4
5
6
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>

<%- js('bootstrap/js/bootstrap.min.js') %>

<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
<%- js('js/ie10-viewport-bug-workaround.js') %>

Notice the use of a JS helper function for our local js files.

Top Menu

In a similar fashion, let’s create the top menu just after the opening <body> tag.

layout/layout.ejs
1
2
3
4
5
6
7
8

// [...]

<body>
<!-- Menu -->
<%- partial('_partial/menu') %>

// [...]

layout/_partial/menu.ejs partial content:

layout/_partial/menu.ejs
1
2
3
4
5
6
7
8
9
<div class="blog-masthead">
<div class="container">
<nav class="blog-nav">
<% for (var i in theme.menu){ %>
<a class="blog-nav-item" href="<%- url_for(theme.menu[i]) %>"><%= i %></a>
<% } %>
</nav>
</div>
</div>

Note the use of the theme global variable. theme is the JS equivalent to the theme’s _config.yml.

Here we are making the menu configurable in the theme’s configuration.

For this to work we will then need to add the config in _config.yml:

_config.yml
1
2
3
4
# Header
menu:
Home: /
Archives: /archives

In menu.ejs we enumerate through all the menu items in the config and create the corresponding links.

The header will be placed just below the menu and contain the blog title and subtitle:

layout/_partial/header.ejs
1
2
3
4
<div class="blog-header">
<h1 class="blog-title"><%= config.title %></h1>
<p class="lead blog-description"><% if (config.subtitle){ %><%= config.subtitle %><% } %></p>
</div>

In here, we make use of the config variable which corresponds to the main _config.yml of the blog. It should have a title and subtitle properties configured.

When inserting the header in the layout, beware of the <div class="container"></div> wrapper:

layout/layout.ejs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<html>

<!-- Head tag -->
<%- partial('_partial/head') %>

<body>
<!-- Menu -->
<%- partial('_partial/menu') %>

<div class="container">
<!-- Blog Header: title and subtitle -->
<%- partial('_partial/header') %>

</div>

// [...]

The footer is all static for now, here is the content of the partial view:

layout/_partial/footer.ejs
1
2
3
4
5
<footer class="blog-footer">
<p>Blog template built for <a href="http://getbootstrap.com">Bootstrap</a> by <a href="https://twitter.com/mdo">@mdo</a>.</p>
<p>Adapted to Hexo by <a href="http://www.codeblocq.com/">klugjo</a>.</p>
<p><a href="#">Back to top</a></p>
</footer>

Main content and sidebar

At this point, we are ready to wrap things up and add the main content as well as the sidebar.

Here is the final layout.ejs:

layout/layout.ejs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<html>

<!-- Head tag -->
<%- partial('_partial/head') %>

<body>
<!-- Menu -->
<%- partial('_partial/menu') %>

<div class="container">
<!-- Blog Header: title and subtitle -->
<%- partial('_partial/header') %>

<div class="row">

<!-- Main Content -->
<div class="col-sm-8 blog-main">
<%- body %>
</div>

<!-- Sidebar -->
<div class="col-sm-3 col-sm-offset-1 blog-sidebar">
<%- partial('_partial/sidebar') %>
</div>
</div>
</div>

<!-- Footer -->
<%- partial('_partial/footer') %>

<!-- After footer scripts -->
<%- partial('_partial/after-footer') %>
</body>

</html>

The body variable corresponds to the content rendered from the different page type templates (cf above).

For the sidebar partial, we are simply going to hardcode the bootstrap template code for now:

layout/_partial/sidebar.ejs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<div class="sidebar-module sidebar-module-inset">
<h4>About</h4>
<p>Etiam porta <em>sem malesuada magna</em> mollis euismod. Cras mattis consectetur purus sit amet fermentum. Aenean lacinia bibendum nulla sed consectetur.</p>
</div>
<div class="sidebar-module">
<h4>Archives</h4>
<ol class="list-unstyled">
<li><a href="#">March 2014</a></li>
<li><a href="#">February 2014</a></li>
<li><a href="#">January 2014</a></li>
<li><a href="#">December 2013</a></li>
<li><a href="#">November 2013</a></li>
</ol>
</div>
<div class="sidebar-module">
<h4>Elsewhere</h4>
<ol class="list-unstyled">
<li><a href="#">GitHub</a></li>
<li><a href="#">Twitter</a></li>
<li><a href="#">Facebook</a></li>
</ol>
</div>

The index file

Now that our layout is in place, we are ready to create the first page type template: index.ejs.

Here is a trivial first version:

layout/index.ejs
1
<span>Content</span>

Useless ? Well, this allows us to test out theme in browser:

1
2
3
4
5
# Verify that everything is alright
hexo generate

# Start hexo server
hexo server

And open your browser at http://localhost:4000/. Tada !

Note: Don’t forget to update the theme in your blog’s config:

_config.yml
1
2
3
4
# Extensions
## Plugins: http://hexo.io/plugins/
## Themes: http://hexo.io/themes/
theme: bootstrap-blog-hexo

Enumerate through blog posts

On the home page we want to display post excerpts.

First of all, let’s enumerate through the posts in our index.ejs:

layout.index.ejs
1
2
3
<% page.posts.each(function(item){ %>
<%- partial('_partial/article-excerpt', {item: item}) %>
<% }); %>
  • Get a list of posts for that page with page.posts
  • Pass an argument in a partial by using <%- partial('name', args) %>

Article layout

Let’s create the article-excerpt.ejs partial and adapt the code to our theme. Here is what I came up with:

layout/_partial/article-excerpt.ejs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<div class="blog-post">

<!-- Title -->
<h2 class="blog-post-title">
<a href="<%- config.root %><%- item.path %>">
<%- item.title %>
</a>
</h2>

<!-- Date and Author -->
<p class="blog-post-meta">
<%= item.date.format(config.date_format) %>
<% if(item.author) { %>
by <%- item.author %>
<% } %>
</p>

<!-- Content -->
<%- item.excerpt || item.content %>

<!-- Only display the Read More link if we are displaying an excerpt -->
<% if(item.excerpt) { %>
<p>
<a href="<%- config.root %><%- item.path %>">
<%= theme.excerpt_link %>
</a>
</p>
<% } %>
</div>

- Link to the full post:

The link to the full post is created by concatenating config.root (config option which really shoud be equal to /) and item.path which is the relative path or link to the full post.

- Post author

By default, Hexo does not have any author property in it’s post variable. But we can add whatever variable we want to the front matter.

If you want an author name to be displayed for a post, the front matter for your post should look something like

1
2
3
title: Hello World
author: Klughertz Jonathan
---

- Item excerpt vs. Item content

When writing a post with Hexo, you can use a <!-- more --> tag to delimit the excerpt from the content. In our case, we are displaying the excerpt since this is a list of posts.

The user then has the possibility to click on a post’s title or on the read more link to view the entire post.

- Read More text

I have added a new property to my theme’s config, do not forget to add it to yours:

_config.yml
1
2
# Read More text
excerpt_link: Read More

Hopefully the rest of the code is easy enough to understand. At this point, I suggest you write a few additional posts than the default Hello World and play around with the results.

Pagination

The last thing we are going to tackle in this section is the pagination for the home page

Let’s start by including yet another partial to our index.ejs:

layout/index.ejs
1
2
3
4
5
<% page.posts.each(function(item){ %>
<%- partial('_partial/article-excerpt', {item: item}) %>
<% }); %>

<%- partial('_partial/pagination') %>

And finish by writing our pagination partial view, layout/_partial/pagination.ejs:

layout/_partial/pagination.ejs
1
2
3
4
5
6
7
8
9
10
<nav>
<ul class="pager">
<% if (page.prev){ %>
<li><a href="<%- config.root %><%- page.prev_link %>">Previous</a></li>
<% } %>
<% if (page.next){ %>
<li><a href="<%- config.root %><%- page.next_link %>">Next</a></li>
<% } %>
</ul>
</nav>
  • page.prev: Previous page number. 0 if the current page is the first.
  • page.next: Next page number. 0 if the current page is the last.
  • page.next_link and page.prev_link are self explanatory.

You can adjust the post per page in the main config (per_page property) if you don’t have enough posts to see the pagination in action.

That’s it for today, in the next section of this tutorial, we will finish all the remaining pages of the blog.