Tips for Migrating from WP to Hexo

My life! I have almost finished moving my Wordpress blog over to Hexo and it’s been a mission.

Don’t get me wrong - I still believe in all of the reasons why static sites are great but you need to be aware of some of the limitations in using a plugin to move your data and just how much work it can be to finish it all off. Hopefully, though, I can share some tips to save you some pain, or at least point out some of the issues.

Begin by downloading the posts and images from your Wordpress site; without those, you are dead in the (post) water.

Let’s begin with the cosmetic things before moving onto the areas which can be a bit trickier.


The conversion plugin creates headings using hyphens to underline them, but I prefer to use pound signs, so that required some editing.


to this:

## Heading

Paragraphs don’t always break

I’ve found that some of my original posts end up as one big mass of text because all my paragraph breaks are lost. One trick I used was to refer to my original blog post, remember the name of each word starting each paragraph and then insert my breaks in the markdown at those points. It still takes a while to do and I am not certain I was 100% successful.


All my tables went skew-whiff! You can try to fix them with tools like the tables generator but honestly, it’s easier just to use your existing site and take a screenshot, using it as an embedded image. Of course, you can no longer copy and paste the text, but it will save a lot of time.

Spaces added to bulleted and numbered lists

Even something as innocent as lists may require some tweaking. You will find that when you have lists like:

  1. This
  2. And, this


  • This

Then the converter will add extra spaces in - one space for the numbered list and two for bullets. Specifically, it results in:


Not a huge problem but if you like things just so, search and replace the double spaces with single etc. in your editor to correct them.

Some characters are escaped with a slash

I found things like the pound sign #, hyphen -, left square bracket [ and backslash \ would be escaped with a slash. Sometimes. Frustratingly, it isn’t consistent so you need to manually check and alter your text.

Moreover, if you are a coder, watch out for those square brackets used in arrays because this comes up a lot. When embedding code, we definitely don’t want to escape any of that at all.

Images are painful

Now we get onto the things that are going to leave you pulling your hair out and easily cost me the most time.

Different ways of referencing images

You may notice that depending on your Wordpress install, the pictures could be referred to in this style:


or sometimes like this:

\[caption id="attachment_1625" align="aligncenter" width="152"\][![The Fav Icon](]( The Fav Icon\[/caption\]

which is a bit odd. Be aware of this when looking for items to replace if you are tempted to use the search features of your editor.

Keeping images in same folder

OK, so you’ve decided to have a big folder of images and just refer to them that way. Replace each of your image references in your posts with this which uses the img plugin.

Using a folder with each post for images

Since I used the folder method to organise my images, this requires a bit more work.

Briefly, you need to go to each of your posts and work out which images are referenced, then move them into the pre-created folder. Note also that if your Wordpress install created variants (images of different sizes), then you might want to pick and choose which you use. For me wanting my page loads to be fast, I tried to keep my images under 50KB so made my choices based on that.

Now, using this method, how you refer to an image works in a slightly different way for folder based graphics (note - remove the backslashes if you copy this code):

{\% asset_img image-filename.jpg "Tooltip text to show" \%}

Images in posts

For some reason, I believe if you are using the folder method, pages don’t play nicely with how I have shown you. In those cases, you need to reference the images using absolute paths and using the img block instead. Here’s an example:

{\% img "/mainfolder/index/image.jpg" "Image Tooltip" \%}

Making sure you don’t miss any images out

If you’ve installed something like Bash for Windows, you can use a command like this to zip through all of your files (markdown files, in my case), hunting for any which have references to images that haven’t been changed to use the hexo codes. Here’s an example:

$ grep -R "caption" *.md\[caption id="attachment_948" align="aligncenter" width="300"\][![4th Website's Screenshot](]( ASP.NET Website #4 Screenshot\[/caption\]

You can see in my example that the filename was so I knew just where to hunt for missed ones.

That’s really handy because I bet no matter how careful you are, you will miss some - I know I did.

Installing (and uninstalling) plugins

The real power of hexo comes from the plugins. The basic command to install one is like this:

> npm install <name> --save

where <name> is the name of the plugin which can be found here. Handily, many of the GitHub pages which host them have nice instructions which explain what you need to do.

To uninstall, do this:

> npm uninstall <name>

and here, you can find out more information.

Useful plugins

Here are the ones that I presently use and think are handy:

Setting up default posts

If you want to be able to specify how your basic posts look, you can alter the scaffolds for them in the folder \scaffolds.

Here you will find three files:

  2. and,

I wanted my posts to look like this so edited this way:

title: {\{ title }\}
date: {\{ date }\}

{\% asset_img "header.jpg" "Header Image" \%\

<\!-- md -\->

Worth mentioning is the inclusion of the default image and the footer at the bottom, which I will come to soon. Remember, if you want to do this, edit it before you generate your posts from the Wordpress export so that they take effect when you do the conversion. Also, take out my \ characters - they are just here to stop hexo interpreting them in this post.


These are really useful and I have already hinted at them twice. They basically work by adding an XML comment like this in the bottom of your posts/pages:

<\!-- md -\->

Again, remove the backslashes (\).

Now, create a file in the folder source/_template named and place what you want in there. Look at the bottom of this post to see what I have currently got in mine.

How I arrange my screens

To make editing files easier, I lay out my screen real estate as 5 windows like this:

  1. Editor (VS Code)
  2. Command line windows x 2. One for serving the files and the other to handle my Git commands.
  3. Explorer window for the images I am searching through (the big folder with all images in) and,
  4. Explorer window which I use to visit each folder of images that I am pasting into.

I basically edit my markdown in VS Code, copy my images into their folder and check the text with the web server which is automatically updated in command window.

It’s worth mentioning that if you run the web server before copying the images in, they won’t be reflected in your browser, but any text or category changes will.

Finally, I check my files in using Git.

How I edit the files

I find that if I do all of one thing, I get quite quick at it and the work is produced faster. That means I do all headings, then all images, then all paragraphs etc. in each file. It might be different for you, but when you are editing 169+ posts as I did, you try any ol’ trick you can.

Tracking down errors

I’d advise not editing too many files at once without checking that the HTML can be produced for them. Errors are terse at best and it doesn’t show you which file caused the problem, so narrow that down by re-creating your site after each one or two.

Failing that, you can look here for some hints at what might be stopping your files from being produced. For example, one of my favourite errors is not closing code blocks like this, so watch out for that kind of thing.

{\% codeblock line_number:false \%}
There is a missing: {\% endcodeblock \%}

Automating build and deploy

On Windows, I have two batch files that I use all the time - serve.bat and deploy.bat which you might want to copy:

REM serve.bat
hexo clean && echo "{}" > db.json && hexo generate && hexo serve
REM deploy.bat
hexo clean && echo "{}" > db.json && hexo generate && hexo deploy

The key thing to notice is that I write the empty braces to the db.json file. There is a bug which keeps popping up which complains about not being able to open db.json and that makes it go away. These are placed in the root of my folder tree, ready to use and as a habit, I like to remove everything before rebuilding - just to be safe.

Showing code

There are various options in Markdown for this, but if you don’t mind the line numbering, just do something like this (minus the backslashes (\)):

...some HTML here

The syntax highlighting works great. If I am showing commands, though, I often don’t want the line numbers so just include a code block like this:

{% codeblock line_number:false %} {% endcodeblock %}

There are lots of options here, including setting the language, so do check the docs. As ever, remove the backslashes (\) - they are just to stop hexo interpreting them.

Category fixes

I don’t know about you, but I noticed that I had been a little inconsistent with my categories - this is a good time to fix those, so edit all of your posts and choose a scheme which better reflects what you write about over time.

Changing the logo and favicon

I used (and recommend) the cactus theme, which I am really happy with, but one thing I couldn’t resist was changing the icon from a cactus to a moon. To do that, at least for this theme, you need to replace the png and ico files located in \themes\cactus\source\images. Gimp worked really well for this allowing me to create a transparent PNG and fav icon using the same image.

We’re almost done.

Configuration files

I was going to end here but want to add one more thing about configuration files. Make sure you exclude _config.yml from your git repo with this in your .gitignore file:


Then, create an example YML file which contains all of your settings, minus the values; I called mine _config-example.yml for instance. In case you were wondering, here is my complete file.

# Hexo Configuration
## Docs:
## Source:

# Site
title: 'Logical Moon'
subtitle: 'A blog about code, tips, technology and randomness'
description: ''
keywords: blog,personal,code,web
author: Stephen Moon
language: en
timezone: 'gb'

## If your site is put in a subdirectory, set url as '' and root as '/child/'
root: /
permalink: :year/:month/:title/
trailing_index: true # Set to false to remove trailing index.html from permalinks

# Directory
source_dir: source
public_dir: public
tag_dir: tags
archive_dir: archives
category_dir: categories
code_dir: downloads/code
i18n_dir: :lang

# Writing
new_post_name: # File name of new posts
default_layout: post
titlecase: false # Transform title into titlecase
enable: true # Open external links in new tab
field: site # Apply to the whole site
exclude: ''
filename_case: 0
render_drafts: false
post_asset_folder: true
relative_link: false
future: true
enable: true
line_number: true
auto_detect: false
tab_replace: ''

# Home page setting
# path: Root path for your blogs index page. (default = '')
# per_page: Posts displayed per page. (0 = disable pagination)
# order_by: Posts order. (Order by date descending by default)
path: ''
per_page: 15
order_by: -date

# Category & Tag
default_category: uncategorized

# Metadata elements
meta_generator: true

# Date / Time format
## Hexo uses Moment.js to parse and display date
## You can customize the date format as defined in
date_format: YYYY-MM-DD
time_format: HH:mm:ss
## Use post's date for updated date unless set in front-matter
use_date_for_updated: false

# Pagination
## Set per_page to 0 to disable pagination
per_page: 15
pagination_dir: page

# Include / Exclude file(s)
## include:/exclude: options only apply to the 'source/' folder

# Extensions
## Plugins:
## Themes:
theme: cactus

# Deployment
## Docs:
type: ''

enabled: false
id: ''


Home: /
Projects: /projects
Categories: /categories
Search: /search/
About: /about
RSS: atom.xml

show_all_posts: true

per_page: 15
order_by: -date

dir: source/_template/ # Base directory of template markdown
verbose: true # If you want to check the path of markdown that use <!-- md --> tag , please set the true.

Some things I have left in like links to my Github repo etc but obviously all my Google Analtics code and deployment info is not there.

The theme_config section is where I override some of my theme’s defaults - you will need to check out your theme’s docs to see if that is of any use to you, though.

I think that’s it - this is much bigger than I had planned, but like I said, this was a huge job but hopefully worth it. If you have any queries, please contact me and I will try to help.

Hi! Did you find this useful or interesting? I have an email list coming soon, but in the meantime, if you ready anything you fancy chatting about, I would love to hear from you. You can contact me here or at stephen ‘at’