In this multi part tutorial, you will learn how to create an Hexo Theme from scratch.

In Part 1, we have setup the project and the Home Page. In this section, we are going to build on what we have learnt to finish up all the remaining pages.

The Post Detail page

Let’s continue where we left off in part 1 and create the post detail page.

As we have seen, to render the detail page, Hexo will look for a post.ejs file in our /layout/ folder.

Here is my post.ejs:

layout/post.ejs
<%- partial('_partial/article-full', {item: page}) %>

To keep the code organised, the actual code is deferred to a _partial/article-full.ejs that we are going to create now:

layout/_partial/article-full.ejs
<div class="blog-post">

<!-- Title -->
<h2 class="blog-post-title">
<a href="<%- config.root %><%- item.path %>">
<%- item.title || item.link%>
</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.content %>

<hr />

<!-- Tags and Categories links -->
<%- partial('article-tags', {item: item}) %>
<%- partial('article-categories', {item: item}) %>

</div>

This template is almost the same as _partial/article-excerpt.ejs, except that:

  • We are displaying the full content with <%- item.content %> and not the excerpt.
  • There are two additional partial views at the bottom, one for tags and one for categories. We will jump into these right now.

Post Tags

Let’s create the partial that will render the list of tags for a post: layout/_partial/article-tags.ejs.

What we want is a list of #tags with links to the corresponding ‘tag page’ which will display all the posts with that tag.

layout/_partial/article-tags.ejs
<% if (item.tags && item.tags.length){ %>
<%
var tags = [];
item.tags.forEach(function(tag){
tags.push('<a href="' + config.root + tag.path + '">#' + tag.name + '</a>');
});
%>
<div class="blog-tags-container">
<span class="glyphicon glyphicon-tags"></span>
<%- tags.join(' ') %>
</div>
<% } %>

Nothing complicated, we are enumerating through all the tags in post.tags and displaying them one after the other. I have added a hashtag before each tag and an icon before the list for good measure.

Post Categories

The layout/_partial/article-categories.ejs partial is very similar:

layout/_partial/article-categories.ejs
<% if (item.categories && item.categories.length){ %>
<%
var categories = [];
item.categories.forEach(function(category){
categories.push('<a href="' + config.root + category.path + '">' + category.name + '</a>');
});
%>
<div class="blog-categories-container">
<span class="glyphicon glyphicon-folder-open"></span>
<%- categories.join(' / ') %>
</div>
<% } %>

No explanation required.

Post CSS

As you may have noticed, there are 2 new CSS classes used for tags and categories styling. Here is the code for it, added to blog.css:

source/css/blog.css
.blog-tags-container, .blog-categories-container {
margin-top: 30px;
font-size: 20px;
}
.blog-tags-container span.glyphicon, .blog-categories-container span.glyphicon {
margin-right: 20px;
}

The detail page for page type content

This is an easy one. The ‘page type’ pages will be the same as ‘post type’ pages. Feel free to customise it as an exercise, but here is mine:

layout/page.ejs
<%- partial('_partial/article-full', {item: page}) %>

The Archive page

The archive page will display a list of posts in a more condensed way than the index page. The base will be the same as the index though:

layout/archive.ejs
<% page.posts.each(function(item){ %>
<%- partial('_partial/article-archive', {item: item}) %>
<% }); %>

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

The article-archive partial view

As always, the actual work is in the partial view. I used article-excerpt as the base and stripped it down to just the title, date and author:

layout/_partial/article-archive.ejs
<div class="blog-post">

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

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

</div>

Careful eyes will have noticed the new CSS class that I have created for archive titles (they were too big for my liking):

source/css/blog.css
.blog-post-title-archive {
margin-bottom: 5px;
font-size: 25px;
}

Tags and Categories Pages

The last two pages we need to work are for the list of posts that correspond to a tag and a category. Now if you remember well:

Template Fallback Page Description
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.

The fallback page for our tag.ejs and category.ejs is archive.ejs. Because I don’t see any major difference in between these 3 pages, we are just going to use the fallback to archive.ejs. Less code to write, less duplicate code, easier to maintain.

But in order to differentiate our 3 pages, we are going to add a title to the archive page:

layout/archive.ejs
<%
var title = '';
if (page.category) title = page.category;
if (page.tag) title = page.tag;
if (page.archive){
if (page.year) title = page.year + (page.month ? '/' + page.month : '');
else title = "Archives";
}
%>

<% if(title) { %>
<h2 class="blog-archive-title"><%- title %></h2>
<% } %>

<% page.posts.each(function(item){ %>
<%- partial('_partial/article-archive', {item: item}) %>
<% }); %>

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

Now we have a nice title that describes what our archive page is for.

And here is the CSS that goes with it:

source/css/blog.css
.blog-archive-title {
margin-bottom: 50px;
}

This section of the tutorial was pretty straightforward, simply building up on concepts defined in Part 1. I encourage to play around with the theme and hack it to your tastes.

In Part 3, we will add a comment section, analytics, widgets and polishing things up. See you there !