I Finally Decided to Migrate My Blog from Hexo to Hugo

2026-10-04 10:30≈ 1112Words≈ 6Minutes

I used Hexo for many years. At first it was simple, did what I needed, and its theme was easy to customize. Over the years, though, I kept adding features, tweaking styles, and filling in plugins and scripts to suit my habits. I had customized far too much.

Looking back, the site still ran, but it had become increasingly fragile. The last time I upgraded to version 6.3, all my customizations kept me busy for ages. This migration isn’t because Hexo is bad. I’ve simply changed too much, and the cost of maintaining those customizations has started to outweigh the convenience they bring.

Why I decided to migrate

There was more to maintain than just the article pages. The Chinese, English, and Japanese sites each had their own configuration, and publishing meant building and deploying them one by one. The homepage, yearly archives for essays, novel categories, tags, language switcher, word counts, and About page all had their own theme logic too. Whenever I wanted to change one small thing, I first had to work out whether it would affect the other language sites or other pages.

The deployment process had accumulated plenty of baggage as well. The old GitHub Actions workflow needed separate secrets for the three sites, ran Hexo, then switched configurations and build outputs with scripts. It worked, but the dependency versions, Node.js runtime, and custom scripts all needed ongoing attention. A blog should let me focus on writing, not make me worry about the build and publishing process every time I update it.

I’m getting older, and sometimes I can barely work up the interest to write in the first place. Then there’s the worry that after not writing for half a year, I won’t remember whether all the configuration parameters are still okay. I also worry that one day I’ll push a change, have to go to GitHub to hunt down an error, and then tweak things all over again.

So I wanted to sort out the content and presentation logic and rely more on the content management, Markdown parsing, and syntax highlighting Hugo already provides. The personal design I needed to keep could live in a small number of templates.

How I migrated

I have ChatGPT to thank for giving me the confidence to try this migration. With large language models, I finally dared to give it a shot. To avoid affecting the Hexo site that was already running, I asked Codex to migrate and validate the Hugo project in a separate directory, without overwriting any files in the Hexo project. We first checked the old site’s configuration, theme modifications, plugins, article front matter, and GitHub deployment workflow, then used the old site’s generated output as a baseline for comparing links and page structure.

Articles went into Hugo’s content directories for Chinese, English, and Japanese, while keeping their original titles, categories, tags, dates, and URLs wherever possible. I also reorganized the content: the homepage lists only experience articles, essays are archived by year, and novels are grouped by category. All of those customizations I made back then have become pitfalls I have to deal with now.

Hugo generates article details such as word counts and reading time from the content. I also checked the interface and Markdown page by page. The language switcher, table of contents, and previous and next links moved into Hugo templates, while Hugo’s built-in Goldmark and Chroma handle fenced code blocks.

Hugo-generated sitemap could not be parsed

I compared the three sitemaps before and after migration: the Chinese site had 243 entries in both, the English site went from 90 to 92, and the Japanese site from 217 to 219. After URL decoding and Unicode normalization, every existing article URL had a match: 176 Chinese articles, 42 English articles, and 162 Japanese articles. The Japanese sitemap also listed one article that wasn’t in the old site’s sitemap. A few URLs looked different because of percent encoding and other string-level details, but their normalized paths matched.

After migrating, I also found that although the Chinese site’s sitemap opened, Google Search Console couldn’t parse it. The reason was that all three language sites had originally been generated in one multilingual build. Hugo produced a sitemap index and sitemaps in language subdirectories, while the deployment targets were actually three separate websites. As a result, the Chinese sitemap ended up at /cn/sitemap.xml, even though the pages listed in it used the Chinese site’s root paths. Its location didn’t match the deployment structure. In a browser, the content running together could also make it look as if the file wasn’t XML; the core issue was how the sitemap was generated and deployed.

The fix was to build Chinese, English, and Japanese separately, each with its own Hugo configuration. Each build enables just one language and generates a standard sitemap.xml at the root of that site’s publishing directory, without a multilingual sitemap index. The three sites still use their original domains and deployment repositories, and don’t need to be linked through their sitemaps. After the change, the sitemap displays as XML in the browser, and Search Console can crawl it after I resubmit the sitemap at each site’s root.

Still publishing with GitHub Actions

After switching to Hugo, I kept using GitHub Actions for deployment. The existing deploy workflow runs when changes are pushed to the hugo-migration branch: it builds each site separately with its Chinese, English, and Japanese configuration, then deploys them to the same target repositories as before. Each published site’s root contains its own sitemap.xml, for example /sitemap.xml on the main site, /en/sitemap.xml on the English site, and /jp/sitemap.xml on the Japanese site.

Deployment still uses the three Actions secrets already in the blog repository. From now on, when I finish an article, I can commit and push the Hugo branch as usual, and the same workflow will run. I don’t have to generate three versions by hand. Everything is just as before: the same repositories, the same passwords, and all the same problems—except that I open the Hugo project to commit instead of the Hexo project.

After the migration

Before, doing something like this might have taken a whole day or even longer. With Codex, it took only half a day. Two of those hours went into sorting out code block formatting and the sitemap, so without those issues, maybe the whole thing could have been done in two hours.

Hugo really is faster than Hexo, but that’s not the most important thing. I’m simply too lazy to keep track of all the customizations I made, and I can’t remember them all. I’m worried that some future update will bring everything crashing down. Hahahaha.