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.
- Part 1: Setting up the project and creating the blog’s main index page
- Part 2: Finishing up the remaining pages
- Part 3: Wrapping up with Disqus comments, Google Analytics and the widgets
- The very basics of blogging with hexo. If you are completely new, check the getting started guide on hexo.io.
- Some knowledge of Bootstrap
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.
Let’s get started with a brand new hexo installation.
Note: If you want to save the theme to git (as you should), initialise git inside
Here are the files and folders we will need to get started:
Create these 2 folders and the
/layout/will contain all our EJS templates
/source/will contain our assets (CSS files, external scripts and libraries)
_config.ymlcontains our theme configuration. Leave it empty for now.
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.
Before we write our first template file, let’s look at the basic of Hexo blog generation.
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:
||None||This the home page of the blog, the main entry point. In our case it will display a list of blog excerpts.|
||This is the detail page for posts. Here we will display only one post in full, with a comment section.|
||This is the detail page for pages. Same as post but for ‘page type’ posts.|
||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.|
||This is the category page. Similar to the archive page but filtered for one category.|
||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
During the generation is Hexo will look for files named
page.ejs and so on. These templates are then rendered to create the static HTML pages.
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
<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.
Inside all our templates, we have access to some variables that are injected by the hexo engine. Here are some of them:
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 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.yml of the blog.
We will start by creating the
/layout/layout.ejs file discussed above.
Let’s start by creating a layout.ejs file and inserting the
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:
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:
And create the content of the new
Notice the use of a JS helper function for our local js files.
In a similar fashion, let’s create the top menu just after the opening
layout/_partial/menu.ejs partial content:
Note the use of the
theme global variable.
theme is the JS equivalent to the theme’s
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
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:
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:
The footer is all static for now, here is the content of the partial view:
At this point, we are ready to wrap things up and add the main content as well as the sidebar.
Here is the final
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:
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:
Useless ? Well, this allows us to test out theme in browser:
And open your browser at http://localhost:4000/. Tada !
Note: Don’t forget to update the theme in your blog’s config:
On the home page we want to display post excerpts.
First of all, let’s enumerate through the posts in our index.ejs:
- Get a list of posts for that page with
- Pass an argument in a partial by using
<%- partial('name', args) %>
Let’s create the
article-excerpt.ejs partial and adapt the code to our theme. Here is what I came up with:
The link to the full post is created by concatenating
config.root (config option which really shoud be equal to
item.path which is the relative path or link to the full post.
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
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.
I have added a new property to my theme’s config, do not forget to add it to yours:
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.
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
And finish by writing our pagination partial view,
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.prev_linkare 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.