Hexo Upgrade Journey: Six Years Later & Multilingual Support

Hexo Upgrade Journey: Six Years Later & Multilingual Support
Misaka100131. Background & Motivation
My blog (misaka10013.cn) has been running on Hexo 4.2.0 + NexT 6.0.0 since it was first launched — six years without touching the framework. Every time I recently asked an AI to help add a feature or tweak a page, it complained that my versions were too old: outdated dependencies, incompatible plugins, even the config syntax has changed. I should have upgraded long ago, but I kept putting it off.
Lately I’ve had a few new ideas:
1. Multilingual support. I wanted Chinese, English and Japanese versions of the site — first, to practice foreign languages by reading my own posts in different languages; second, for SEO. My posts are hard to find no matter where the search happens, and localized versions are friendlier to search engines.
2. A new theme. I had my eye on the AnZhiYu theme — rich colors, rounded cards, gradient animations, the exact opposite of NexT’s minimalist “indifferent” style. Since I had to change things anyway, why not jump straight to the latest versions and solve everything at once?
2. Framework Upgrade (Hexo 4→8 + NexT 6→8)
Overall Plan
After an initial discussion with the AI and a review of the blog’s current state, we settled on an execution plan: create a next8 branch off master, do the upgrade, theme migration, feature porting and multilingual development all on the branch, and only merge back to master for production once everything passes testing. The live site keeps running the old version throughout, completely unaffected.
Division of Labor
- Me (Misaka): defining the plan, reviewing each step, making the final calls
- AI: doing the actual work, assessing impact, reporting pitfalls
Upgrade Checklist
| Item | Old version | New version |
|---|---|---|
| Hexo | 4.2.0 | 8.1.2 |
| NexT | 6.0.0 (git clone) | 8.29.0 (npm package) |
| Node.js | 16.x | 22.22.2 |
| Deployment | CNB cloud build | Same (node:18→node:22) |
3. The Multilingual Approach
Aligning Requirements
Working with an AI means aligning on the right level of detail. I had the AI propose concrete implementation methods for my rough requirements, then used a Socratic approach — it kept asking me questions until the requirements were fully pinned down:
- Trilingual UI (CN/EN/JA) — ✅ required
- Language switcher always visible in the sidebar across the site — ✅ required
- Each language home page shows only that language’s posts — ✅ required
- Post pages: has translation → jump; no translation → button disabled + in-post notice — ✅ compromise
- Translation is the user’s job; AI only builds the framework — ✅ clear
- Empty Japanese site initially is acceptable — ✅ accepted
Final Approach: Single Build + Thin Script
The final approach is a single build plus one scripts/i18n-blog.js that handles all the logic.
1. Translated Post Routing: the post_permalink Filter (priority 9)
- Problem: by default every post shares the same URL structure, so language versions can’t be distinguished.
- Approach: intercept and rewrite the permalink when Hexo generates it.
- Default-language posts (e.g.
zh-CN) keep their original path, e.g./p/xxx.html. - Translated posts (e.g.
en) are forced to/en/p/xxx.html.
- Default-language posts (e.g.
Result: each language version of a post gets an independent, clean URL, which helps SEO and makes language switching straightforward.
2. Language-Specific Listing Pages: Overriding the index / archive / category / tag Generators
- Problem: Hexo’s default generators mix all posts together for the index and archives, so they can’t be separated by language.
- Approach:
- Rewrite the generator logic.
- For each language, generate that language’s home, archives, categories and tags pages.
- Filter to only that language’s posts when generating.
- If a language has no posts, generate a placeholder page instead of returning 404.
Result:
- Each language site has its own home page and content listings, completely independent.
- Visiting
/en/shows a placeholder instead of an empty-page error — a much better experience.
3.1 Language Switcher (Select Dropdown) & In-Post Language Banner
The script analyzes whether the current page has versions in other languages.
Has translation: the switch option is highlighted/clickable and points to the correct translated URL.
No translation: the option is grayed out (
disabled) with atitletooltip like “This post has no English version yet”.When other language versions exist, a notice banner is inserted at the top of the post body.
E.g.: “This post also has an English version: English Version”
On the English page it shows: “The Chinese original of this post: 中文原文”
Result: language switching is driven entirely by whether the content actually exists, so users never land on a missing page — a much smarter interaction.
4. Core Implementation: the i18n_map Generator
- Problem: the frontend JS needs a dictionary for instant language jumps.
- Approach: additionally generate a static JS file at build time:
/js/i18n-map.js.
It contains a mapping table, e.g.:
1 | window.I18N_MAP = { |
Result: this data links each post’s unique ID (abbrlink) to the routes of all its language versions. The frontend reads the table directly for instant switching — no server round-trip needed, faster response.
5. Overall Workflow
With this script orchestrating, Hexo’s build order is:
1 | graph TD |
| Stage | Technique | Output | Frontend result |
|---|---|---|---|
| Routing | post_permalink filter |
Per-language post URLs | Clean URL structure |
| Listing | Override 4 generators | Per-language home/archives/categories/tags | Content isolated by language, no 404s |
| Page decoration | after_render:html |
Modified HTML | Smart switcher + translation banner |
| Data layer | i18n_map generator |
/js/i18n-map.js |
Frontend routing dictionary |
Translation Workflow (my process going forward)
1 | AI drafts the translation → I refine it → save to source/_posts/en/ |
Switcher Interaction Logic
| Scenario | Behavior |
|---|---|
| Post has a CN/EN/JA version | Clickable, jumps to that language version |
| Post has no version in that language | Button grayed out/disabled, hover tooltip “This post has no version in that language” |
| Post has other language versions | Banner at top: “🌐 This post also has other language versions: English” |
| Language site home page | Shows only that language’s posts |
4. Theme Test Drive (AnZhiYu)
Zero-Contamination Coexistence
Without touching the existing NexT config, I ran two local servers in parallel, so I could gradually migrate the old theme’s custom settings based on what each page needs:
1 | Port 4100 → NexT theme (hexo server -p 4100) |
_config.anzhiyu_test.yml switches themes with a single line of config, so I can flip back to the minimalist style any time:
1 | theme: anzhiyu |
At startup Hexo merges the two config files; AnZhiYu overrides the theme field while everything else (posts, the i18n script, etc.) is shared from the main config.
AnZhiYu Config System
Important: all AnZhiYu settings live in _config.anzhiyu.yml at the blog root — never touch files inside themes/anzhiyu/.
Feature Migration Checklist
A migration list for the old blog’s custom features — some carried over to the new theme, some dropped.
| Feature | Status on AnZhiYu | Notes |
|---|---|---|
| Live2D mascot | ✅ Kept | Inject autoload.js; music player moved to top-left to make room |
| Language switcher | ✅ Ported | Injected into the right-side floating bar; same disabled/clickable logic |
| Copy button | ❌ Dropped | The new theme has its own |
| Crash fake-out | ❌ Dropped | The new theme offers other forms |
| Reading progress | ❌ Dropped | The new theme has its own |
| Comments/friend links/menu | Migrating manually | Moving item by item from the NexT config |
5. Current Config & Operations Guide
Multilingual Config
Edit these fields in _config.yml:
1 | # Language order: the first one is the default (Chinese) |
Writing a New Post
Chinese post (default language):
Location: source/_posts/
Example: source/_posts/我的新文章.md
front-matter example:
1 |
|
No lang field needed; abbrlink is auto-generated at build time.
English translation (requires the Chinese original):
Location: source/_posts/en/
Example: source/_posts/en/my-new-article.md
front-matter example:
1 |
|
Key: lang: en is required, and abbrlink must match the Chinese original.
Japanese post (empty site for now; add when translated):
Location: source/_posts/ja/
front-matter needs lang: ja + the same abbrlink.
Local Preview
NexT theme (port 4100):
Run in the blog root:
1 | cd D:\1\coding\hexo-blog-master |
Visit http://localhost:4100
AnZhiYu theme (port 4200):
Run in the blog root:
1 | cd D:\1\coding\hexo-blog-master |
Visit http://localhost:4200
Note: after changing _config.anzhiyu.yml or files under source/css/ etc., restart the server to apply. Press Ctrl+C to stop, then re-run the command above.
Publishing
CNB cloud-native build: pushing to master auto-deploys. No local hexo g needed.
1 | cd D:\1\coding\hexo-blog-master |
Note: git push inside the sandbox fails with credential errors — run the commands above in a local terminal (cmd/PowerShell).
Previewing During Config Migration
The live site is still on NexT. The AnZhiYu test-drive config lives in the following files, not yet committed to master:
| File | Description |
|---|---|
_config.anzhiyu.yml |
AnZhiYu override config (1342 lines) |
_config.anzhiyu_test.yml |
Test-drive switch (single line: theme: anzhiyu) |
source/css/anzhiyu-custom.css |
AnZhiYu-specific CSS |
source/js/i18n-switcher.js |
Language switcher button JS |
themes/anzhiyu/ |
The AnZhiYu theme itself |
Once I finish migrating the menu, friend links, comments and other configs, I’ll decide whether to make AnZhiYu the production theme.
6. TODO & Outlook
- SEO work: hreflang alternates, multilingual sitemap entries, robots.txt — searching “misaka10013” currently surfaces GitHub first; the blog’s visibility needs work
- Translating key posts: pick important posts, AI drafts, I refine, gradually enriching the English site
- AnZhiYu goes live: decide whether to switch once config migration is done
- OSS key rotation: the plaintext keys in the deploy section are in git history — rotation recommended



