Gangmax Blog

From Octopress to Hexo

I have used Octopress for my blog from 2011. It’s been a while that I want to switch my blogging system from Octopress to something else. This is finally done now: I switched my blog from Octopress to Hexo in the recent days. What you see now is generated by Hexo instead of Octopress.

Why I want to switch?

Octopress is a very good blogging system. However, it has some serious problems as time going:

  1. No support anymore. I was using “Octopress 2”, which is not developed since 2013. The final update of “Octopress 3” is in 2016. Octopress 2 is using “Ruby 1.9” which is very old. And a bigger issue is the “pygments.rb (0.2.4)” gem dependency of Octopress 2, which requires “Python 2.7”. “pygments.rb” is a Ruby wrapper of Python “Pygments“ library, which is used to generate code syntax highlights of code block in blog post. After Ubuntu 14.04, running “pygments.rb” always gets issue(details can be found here). Every time I setup a new blogging environment, it took me a lot of time to make it work.

  2. Performance issue. It takes more about 2 minutes now for Octopress to generate all the website content to preview(I have 500+ posts at this moment). Each time I update something in a post, I need to wait another 2 minutes to see the result. This is distractive and really bad for blogging. I need a faster feedback to make a smoother blogging experience.

  3. Octopres 2 “source/master” two-branch implementation is bad. It introduces a lot of uneccessary complexities.

Why Hexo?

The new blogging system I want should have the following capibilites:

  1. Tag support(In Octopress it’s category). I can easily migrate the tags in the existing posts from Octopress to the new blogging system.

  2. Syntax highlight of code block in posts.

  3. Disqus support. I can easily migrate the existing comments in Disqus to the new blogging system.

  4. RSS support. Although RSS is very popular now, but it’s still important to me since I think it’s still the best way to fetch new content of a website.

  5. Minimum changes to my existing posts during the migration. For examples, I don’t need to change the “front matter block” in each post.

First, I want to give “Pelican“ a try, but it seems migrating from Octopress to Pelican is not so easy. For this reason I didn’t start the migration for a long time, until I found “Hexo” from this blog recently.

Hexo is written in “Node.js”. I’m not fimiliar to Javascript as Python, but for me it’s still a better choice than Ruby. More important, it has every capability I need for the new blogging system. As a extra reward, I find it also has a theme for Octopress! With this theme(the one I’m using), most likely you don’t notice the blog website is changed, do you?

Hexo meets every aspect I need except one thing: the code block syntax highlighting is not accurate enough. But I think this is acceptable.

In fact, I suspect the project “Hexo” aimed to replace “Octopress” from day 1. Because:

  1. It supports all the “front matter block” elements of “Octopress”.

  2. The Hexo commands is very similar to the ones of “Octopress”, such as “hexo new” and “hexo generate”.

  3. The naming of “hexo”: “octopress” -> “hexo”, what do you think?

How to?

It took me several days to make it happen. Here are the steps.

1. Initialization

First you need to get “hexo”. If you have “node.js” and “git”, this is very easy.

1
2
3
4
5
6
7
8
9
# Install Hexo in your environment.
npm install hexo-cli -g
# Clone a sample blog website content by using "git" as a start point.
hexo init blog
# Install all the dependencies.
cd blog
npm install
# Start a simple web server for the blog on port 4000.
hexo server

2. Configuration

Similar to “Octopress”, a “_config.yml” file is used to do the configuration, such as:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Site
title: Gangmax Blog
subtitle: '自由之思想,独立之精神'
description: ''
keywords:
author: Max Huang
language: en
timezone: ''

# URL
## If your site is put in a subdirectory, set url as 'http://yoursite.com/child' and root as '/child/'
url: https://gangmax.me
root: /
permalink: blog/:year/:month/:day/:title/
permalink_defaults:
pretty_urls:
trailing_index: false # Set to false to remove trailing index.html from permalinks

As you see, I change the “permalink” part since in my blog, the URL of each post starts with “blog/“.

3. Theme

As I mentioned, I use the “octo“ theme. First, you need to clone the theme code into the “./theme/octo” directory:

1
2
cd blog
git clone https://github.com/jbreckmckye/hexo-theme-octo.git theme/octo

Then update the “blog/_config.xml” file:

1
2
# The default theme is "landscape", replace it with "octo".
theme: octo

After enabling the “octo” theme, the basic web content can be displayed. However the “octo” theme has several issues, I will give more details about the issues and solutions below.

Every theme has it’s own “_config.yml” file. For the “octo” theme, the following content is updated to fix my requirements:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
menu:
Blog: /
Archives: /archives
# Title: /pageName - create using `hexo new page 'pageName'`

# Post social sharing features
social:
twitter: # set to your twitter handle

# Page sidebars
# Components will appear in the order they are specified
sidebars:
recent: true # boolean to display 10 most recent posts
featured: false # boolean to list all posts with tag 'featured'
tags: true
github: false # string to point to a Github profile | false to disable

# Disqus comments
disqus:
enabled: true
shortname: <disqus_account_name>

4. “Tag” display on index page in the “octo” theme

The “octo” theme doesn’t display “tags” on the index page which is required by me. I read the source code of another Hexo theme which has this feature, then add this feature into the “octo” theme:

  1. Enhance the “sidebar.ejs” file by adding the “type == ‘tags’” section, which includes the “partials/sidebar-tags” part.

  2. Create the “partials/sidebar-tags.ejs” file with the following content:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <!-- From: https://github.com/Kaijun/hexo-theme-huxblog/blob/3dbc45041d9ef66f80f633a9e69f20261f734717/themes/huxblog/layout/page.ejs#L155-L168 -->
    <section class="<%=sectionClass%>">
    <h1>Tags</h1>
    <div>
    <ul>
    <%
    let orderedTags = []
    site.tags.forEach(x => orderedTags.push(x));
    orderedTags.sort((a, b) => (a.name.toLowerCase() < b.name.toLowerCase()) ? -1 : 1);
    orderedTags.forEach(function(tag){ %>
    <li><a href="<%= config.root %>tags/<%= tag.name %>/" title="<%= tag.name %>" rel="<%= tag.length %>"><%= tag.name %> (<%= tag.length %>)</a></li>
    <% }) %>
    </ul>
    </div>
    </section>

5. Update “categories” to “tags” in the Octopress posts

Hexo supports both “category” and “tag” concepts: each post can have one category(can be hierachy but only one path) and multiple tags. Octopress only has the “tag” concept of “Hexo” but Octopress named it as “category”. To make the post can be rendered properly in Hexo, I need to update each post by replacing the catagory line like “categories: java jdk” in the “front matter block” to:

1
2
3
tags:
- java
- jdk

I created a Python script to do this:

tagconvert.py
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
36
37
38
39
40
41
42
43
44
45
import glob

def get_source_files(path):
return glob.glob('{}/*.markdown'.format(path))

def get_target_filepath(path, filename):
return '{}/{}'.format(path, filename)

def is_category_line(line):
return line.find('categories:') == 0

def convert_line(line):
if not is_category_line(line):
return line
tags = line[len('categories:'):].split(' ')
result = 'tags:\n'
for tag in tags:
tag = tag.strip()
if (len(tag) > 0):
result = result + "- " + tag + '\n'
return result

def convert_file(source, target):
with open(source, 'r') as fin:
with open(target, 'w') as fout:
index = 0
for line in fin:
if index < 10 and is_category_line(line):
converted_line = convert_line(line)
fout.write(converted_line)
else:
fout.write(line)
index = index + 1

if __name__ == '__main__':
source_path = './source'
target_path = './target'
source_files = get_source_files(source_path)
for source_file in source_files:
target_file = get_target_filepath(target_path,
source_file.split('/')[-1])
convert_file(source_file, target_file)
print('"{}" is converted to "{}"...'.format(source_file, target_file))
print('Done.')

Before run it, you need to create a “source” directory where you put the original posts from Octopress, and another empty “target” directory. Then run:

1
python ./tagconvert.py

The updated posts will be saved in the “target” directory, which have the correct “tags” content for “Hexo”.

After steps “4” and “5”, the tags can be displayed properly.

In “Octopress”, all the images used in the posts are places in the “source/images” directory. In Hexo, it’s the same directory for this purpose. So you just need to copy the image files into the “source/images” directory.

I have an embedded html page with some JavaScript files under the “images” directory. I find some uglified JavaScript file makes Hexo cannot work properly(reports error when running “hexo server”). However if I use the original JavaScript file without uglifying, it works. Not sure what the root cause is, but I use the original JavaScript file to workaround this issue.

7. Code Highlight

The “octo” theme doesn’t support code highlight, which is weird. For me this is a must-have. After some research I realize this is a CSS issue. It took me some time to figure out how to make the CSS show code highlight:

  1. I copy some content from the “landscape” theme related to code highlight into the “octo” theme CSS file.

  2. I remove some useless/redundant content from the “octo” theme CSS file.

  3. I make some adjustment to make the height of the line looks better(the default height of code line of CSS is too big).

8. RSS Feed

The “octo” theme doesn’t support RSS. You just need to run the following command to add the “hexo-generator-feed” dependency:

1
npm install hexo-generator-feed

And update the “theme/octo/_config.yml” file by adding the followign content:

1
atom: true

After that you will have the “http://your.website/atom.xml“ RSS content, just the same relative path as “Octopress” RSS. If someone subscribes your blog, he/she doesn’t need to update the RSS URL.

9. Favicon

Copy the your favicon files into the “theme/octo/source/“ directory:

1
cp favicon.ico favicon.png theme/octo/source/

After that, running “hexo generate” will copy the favicon files into the “public” directory and you website will have the favicon support.

10. Remove the pagination of the “tags” page

I want to do this because:

  1. Octopress behaves like this.

  2. The “Next” pagination link on the “tags” page is shown out of the main section(part of this link is displayed in the sidebar section).

This is done by updating the root “_config.yml” file:

1
2
3
4
# Pagination
## Set per_page to 0 to disable pagination
per_page: 0
pagination_dir: page

GitLab pages configuration

Since my blog is hosted by GitLab Pages, I need to update the configuration of GitLab pages to make it work.

  1. Disable the “Octopress” implemenation

    1. Open the “ctopress” repo, go to “Settings -> General -> Advanced -> Change path”, change the path from “<your_id>.gitlab.io” to something else like “octopress”.

    2. Open the “Octopress” repo, go to “Settings -> Pages”, remove the existing domain(s).

  2. Enable the “Hexo” implementation

    1. Open the “hexo” repo, go to “Settings -> General -> Advanced -> Change path”, change the path to “.gitlab.io”.

    2. Open the “Octopress” repo, go to “Settings -> Pages”, add your domain(s). If it’s your own domain, you need to verify the domain name.

    3. Make sure you enable the “Force HTTPS (requires valid certificates)” option. GitLab Pages provides integration with “Let’s Encrypt” HTTPS certificate.

  3. Add the “.gitlab-ci.yml“ file into the “Hexo” repository

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
image: node:10-alpine # use nodejs v10 LTS
cache:
paths:
- node_modules/

before_script:
- npm install hexo-cli -g
- npm install

pages:
script:
- hexo generate
artifacts:
paths:
- public
only:
- master

Put this file by the side of “package.json” and _config.yml”. This is required by the “GitLab Pages” feature: GitLab will execute the corresponding CI automation actions to generate the blog content when you push new content into the repository.

  1. Go to “Settings -> General -> Visibility, project features, permissions”, change the “Pages” visibility from “Only Project Members” to “Everyone”. Without this step, this blog website can only be viewed by yourself.

Summary

It took me about 3 days to finish this migration. After that, I get the following benifits:

  1. It only takes me a few seconds(not 2 minutes) to see the rendered web content.

  2. Only one “master” branch for all content(no need to take care of the “source/master” branches).

  3. No need to do deployment(“rake deploy”) manually, pushing the updated content into the “master” branch will trigger the deployment automatically.

  4. No need to take care of the “Ruby 1.9/Python 2.7” dependencies, “node.js(10+)” is the only thing you need.

Finally I have to say, Octopress is a great blogging tool which gave my a lot of fun when creating blogs. Although I don’t use Octopress anymore, the spirit of happy blogging from Octopress is still there, and will go on.


Added on 2021-01-12

Today I find the “underscore” character cannot be used as the tag name in Hexo, for example “raspberry_pi” is an invalid tag. If you add such a tag in the post, clicking the tag in the tag cloud section cannot open the category. However the “dash” character is valid. So you can use “raspberry-pi” as the tag.

Comments