Moving From Wordpress to Static HTML Using Hexo

Here I’ll explain how I’ve made my blog 5 times faster and got 340 ms loading time.
It’s a very honest post, and I will explain how dirty the nice things around you are.
If you wanna leave WordPress in the past, but don’t now how to start - this is a ‘step by step explanation’.
Really, I spent half a weekend for the whole process of migration and pretty happy with a result.

The reason

My relations with WordPress started 7 years ago and I spent a lot of time working with it. In 2014 I decided to break all relations with this platform and yesterday finished the process. The last site was my personal blog and one of the stop-factors was my estimate about required efforts for moving from it.
For one year I’ve been dreaming about solving the following issues:

  • Slow loading time
  • Wide range of vulnerabilities of Wordpress.
  • Development for WordPress is painfull, especially in 2017.
  • Hosting static files is cheaper than hosting PHP websites.
  • It isn’t comfortable to write posts in places without (or with a bad) connection to the internet. And in my opinion - airports, planes, and cafes are usually the best places to write something.
  • Logic in themes, global variables are everywhere, hooks.

To be honest, my blog was built on top of the default theme and for 3 years it was affected by several iterations of redesign and some improvements, and as a result, I had something nice on top of a forgotten mess of the endless bunch of weird things. Because it’s my minor project - I didn’t care about security well enough and even was hacked several times. It was a nice experience to find the source of injection and the vulnerability itself (there were two plugins, how predictable, lol) and even wanted to write about it but rejected this idea. I obtained a very strong opinion NO WORDPRESS ANYMORE.
I don’t want to say it’s impossible to write a good web application using wordpress , I just mean wordpress takes much more efforts if you want to make something very simple

Do we have something better?

Yes, we do.
I had at least two options:

  • Generate static files for everything
  • Store all the data in JSON and handle it with SPA.
    The second option requires many efforts to handle SEO, so I strongly recommend that you generate static files instead of generating the whole web page on every request. Benefits are obvious but here is the list:
  • No resources on database and backend language
    • It’s really fast.
    • It can be easily served using any CDN.
    • Easy to deploy, easy to build an environment.
    • It’s absolutely secure. Only the server can be hacked in such a way, but not your website.

Of course, such an approach creates several issues:

  • What should I do if I want the admin panel (Wasn’t actually for me)
  • What to do with comments
  • What to do with contact forms (Didn’t do it, but will do)
  • What to do with a search
  • In which way store the posts
  • How to render all this stuff

Data layer

It’s very hard to name it ‘Data layer’, but it’s the correct definition in this case. Here also were several options:

  • Use relational database (I thought about SQLite, it allows you to make your application portable)
  • Use document-oriented database
  • Use some plain text format, for example, Markdown.
    I’ve chosen the last one. The reasons were.
  • Portable. You can push/pull your app in git and work on every machine with a suitable environment.
  • You can choose from hundreds of editors. The format if easy to use and very flexible.
  • You can keep the post and assets in the same place. Want to remove the post? Just remove the folder!

Now the posts folder structure looks like this (it’s exactly the blog you’re reading):
1

How to render

It was the most complicated decision in this chain. I was looking for many different PHP and JS solutions and found Hexo.
Hexo is a node.js framework which allows you to write posts in markdown and put it into HTML partials (with the template engine, of course) and generate folders and html files for all routes.
For example, I have a post. It belongs to the categories PHP and JavaScript. Hexo allows generating the following structure:

/index.html (home page with a list of the last pages)
/page/2 (actually /page/2/index.html, pagination for posts list)
/categories/javascript (actually /categories/javascript/index.html, list of posts about javascript)
/articles/post_name (the post itself)

Pretty nice, isn’t it?

How to install Hexo

It’s simple as possible. Just install a global npm package hexo-cli :

npm install -g hexo-cli

Afterward you can use Hexo command line interface. Just navigate to your folder and call

hexo init [project_name]

This command will create you a boilerplate of your project. You can use Hexo CLI to generate posts and pages.

The folder structure is pretty simple:
[1](1.png)

How can I move my posts from WordPress to Hexo?

It was a discovery for me. Hexo has a wide range of plugins and I found one for migration! It can be easily installed with npm:

$ npm install hexo-migrator-WordPress --save

In WordPress admin panel you can export a XML file with all the information about your blog including posts. Just put it into your project folder and run

hexo migrate wordpress [your_xml]

That’s it! Now you can find all posts in the source/_posts folder. It looks awesome, but there are several issues you won’t see with a first glance. But you can already generate your application using

hexo clean && hexo g
// always do  cleaning before generation if you change some settings

or you can use a live server

hexo server

It’s great, isn’t it?
…but yes, I mentioned some problems, really:

  1. All pictures will be saved with absolute links to your current blog.
  2. All code snippets will become some mess.
  3. Enjoy escaped special chars in your pages. You will be a little sad if you had HTML or PHP code snippets
  4. Featured images? Forget about them.
  5. Custom post types? Forget about them. BTW, it’s a tricky point.
  6. Probably you will lose some custom things which depend on template / plugins.

Actually issues 1, 2, 3 are pretty easy to solve.
1 - You can write some parser for images, or save manually. I had only 5 images, so the solution was obvious.
2 - You can replace pre tags with regular expression to hexo ones.
3 - Solved with a little script, in average it took 15 minutes, not more.

Featured images is a little bigger problem. I’ll explain the solution in the separate section.

Make sure featured images aren’t supported out of the box. You need to use the plugin or add its support manually. I guess the plugin is unnecessary for such tasks, so I’ve decided to write my personal solution.

In every post, you have something like

title: How to migrate from Chart.js 1.x to 2.x
id: 225
comment: false
categories:
  - JavaScript
date: 2016-05-14 13:15:33
tags:

It’s general information about the post and will be used directly for building. You can feel free to add there whatever you want. I’ve added a featured image in declaration

title: How to migrate from Chart.js 1.x to 2.x
id: 225
comment: false
categories:
  - JavaScript
date: 2016-05-14 13:15:33
tags:
featuredImage: featured.jpg

and changed the article template. You can access a featured image in your post. It’s up to you which way to display it.

post.featuredImage

Templates?

Hexo supports themes. I didn’t dig deep and I’ve already finished HTML so I’ve built my own theme. Templates are pretty light in terms of representational logic and contain just a markup split into sections.
If you want to use some theme - you have to change the application’s _config.yml file.
Just set the

theme: [your template name]

where a name is just a folder name.

Every template has it’s own configuration (also _config.yml) where you can declare some theme-specific data, for example navigation menu links, some information about the build, like less configuration, etc.
The default theme is shipped with js template engine but you are free to use whatever you want, there are many available options. The same with styles, the default preprocessor is Stylus. I moved to Less, because my markup was already in it. (SASS / SCSS is available as an option, of course).
But the most exciting thing is how it works exactly. You need to install one npm package hexo-renderer-less and that’s it! You can just put the Less styles in your styles folder. You can also add some settings in the theme config:

less:
  compress: true

Configuration

Let’s do it step by step and forget about a name. You need to set the following parameters:

  • title
  • description
  • url
  • root

Also, some options. In my opinion it definitely makes sense to have pictures for my post in the same folder, so I’ve used:

post_asset_folder: true

Also, you can configure some representation data, like the amount of posts on a page, default category, etc.
Moreover, you can configure plugins there. I used hexo-all-minifier to minify js and images (I minify styles with Less compiler). And it also needs some simple settings like:

js_minifier:
  enable: true
  mangle: true
  output:
  compress:
  exclude: 
    - '*.min.js'

image_minifier:
  enable: true
  interlaced: false
  multipass: false
  optimizationLevel: 2
  pngquant: false
  progressive: false

It works well. For js it uses uglifyjs, so make sure you’ve already transpiled your ES6 code.

Ok, but what about interactivity?

I mentioned several issues. The first one was search. It can be easily solved using google search. For me this solution was absolutely fine so I’ll move on.

General pages. I have some pages like ‘about me’, ‘my projects’, etc. and there are no posts. You can handle such data easily with layouts. You can add a layout parameter on the top side of every page:

layout: events #for example, events

and create layout template in [your_theme]/layout/events.[your_template_engine_extension]

and… that’s it! You don’t have to add something to settings, declare some relations, anything. And it really drives me!

Comments. I suggest using Discuss, life is too short to have your own comments system in your blog.

Search Optimization

Really I hate SEO. But I have some stable amount of newcomers from google everyday and it was important for me to keep my positions and links. I was really surprised I could save the WordPress structure of links!
Here is how I did it:
In the main configuration of the application I’ve made a little change:

permalink: archives/:id/

I had such structure on my WP site. By default, new posts are coming without the ‘id’ parameter, but luckily the WordPress migration plugin saved them for me. For new posts I’m just writing meaningful information in id (it doesn’t force us to use strings) and new links are looking like /archives/moving-from-WordPress-to-static-html-using-hexo . So I’ve kept old links without getting stuck with .htaccess and realized nice links for future posts.

Writing

Hexo provides a short way for this also, pre-built generator of empty posts. It will create a folder and markdown file. And you can manage the content of the default file, just take a look at a scaffolds folder.
There are three scaffolds by default - for drafts (btw, drafts are available by link, by default so be careful), pages and posts. In every one you can find the default data, for example, for posts:

---
title: Moving From Wordpress to Static HTML Using Hexo
date: 1486404718000
tags:
featuredImage: featured.jpg
categories:
  - JavaScript
---

You can see I have a featured image by definition and it has the same name everywhere, so I’ve added it to a scaffold, also I’ve specified the default category, because I am mostly writing about JS now.

Now you can use

hexo new post [post_name]

and obtain the new empty post with the current data.

Conclusions

As you can see the migration is really as simple as the platform itself. Here I didn’t cover several points like:

  • deployment (it’s an interesting topic which makes sense to be covered in a separate post)
  • theme creation (if this topic will be popular - I’ll write about it)
  • development of your own hexo plugins (the same).

As I mentioned, I am pretty happy with the result and now I think such an approach is really underestimated for many use cases. Thanks for reading!

And screenshot of results:
3