mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
3123 字
9 分鐘
部落格搬家啦!一次從 Hexo 到 Astro 的大遷徙記~
2026-03-01
2026-04-02

沒錯,我的部落格搬新家了 :)

這個部落格原本是基於 Hexo 搭配 ShokaX 主題所建立的,不過最近我把它整個用 Astro 這個現代的 SSG 框架重寫了一遍。因此這邊文章會記錄一下我遷移的過程和一些心得。

 

為何要遷移 ?#

其實真要細說原因的話⋯⋯ 老實講我也不太清楚

但一般來說,主要歸類成以下幾點

1. 原域名被學校封鎖#

如果你和博主有過幾分謀面,想必也應該知道 lolicon.wtf 這 domain name 吧

這個 domain name 是博主在去年年頭買下的,當下根本沒有留意其他因素,只是看到覺得有趣便買了

然而眼睛亮的你們也看到了,這個域名,是以 wtf 作結的

起初我是不怎相信的,直到發了這篇脆文

芋泥2026-02-23

我的domain name再度成功被學校ban掉了

脆友12026-02-23

這是Palo Alto防火牆的URL Filtering 功能阻擋的,那個domain name應該會被各大資安大廠列在Adult才對😑

你可以到這個網站查查: https://urlfiltering.paloaltonetworks.com

脆友22026-02-23

單純他看你不爽吧

脆友32026-02-23

TLD 的問題?

脆友42026-02-24

誰叫你要用這麼名字🤣…

脆友52026-02-24

因為網址有WTF字眼?

脆友62026-02-24

用個正常啲嘅 domain name 唔得嘅

好吧⋯ 徹底被打臉了⋯⋯

這 TLD 很常見於那些成人影片網站上,其內容主要以 18+ 為重。而學校網絡一般都會把不當內容全封,所以⋯⋯ 對,我的網站在上線首天便已被學校的 RADIUS 封掉了⋯⋯

唉⋯ 年少無知啊⋯⋯

2. 維護性問題#

停止更新#

Hexo 的生態系雖然成熟,但許多套件的維護狀況已經不太樂觀。

比如 hexo-admin ,它裏面發文的 API 已經沒有維護了。隨著 Hexo 日益更新下已經完全失效了

而我在用的 hexo-theme-shokax ,也將在不遲於2026年中,宣布停止更新了

ShokaX 主題的為人垢病#

ShokaX 可是一款讓我又愛又恨的主題,其繼承自 Shoka 。雖則好看,設定上並不簡單

首先它雖是屬於 Hexo 的主題之一,然而在安裝上並不能透過 git clone 直接複製。而是需要透過 npm install 安裝。我也明白是因為它直接重寫了整個渲染組件而且新增了一堆自家的邏輯,但這無擬令初學入門的技術門檻大幅提高。

再來我通常都會對正在用的主題大修小補,正因為它不能 git clone ,這些修改我一般也只能在主機上執行,直接深入主題的原始碼。而當每次主題要更新時,也只能先一一備份修改好的設定,待更新後再一一還原。有時候還需要注意上游的修改是否與現有的更改有所衝突,對於我一個不怎打理依賴的人來說可是惡夢。

然後說到更新,這個主題每次一來就是很大的更新,而且每次更新伴隨着不少由輕到重的漏洞,需要花費大量時間慢慢磨合。例如:

  • 0.4.22 更新重寫了 AI Summary

  • 0.5.0 更新觸發了底層邏輯錯誤,VM 佔用了全部資源,還搞得我整台 Proxmox 伺服器卡死了

  • 0.5.2 更新直接把整個渲染器重寫了,伴隨着CSS錯位問題

說實話這樣的一個主題,我是不怎樣放心繼續用下去的。縱然我的主題版本停留在較為穩定的 0.4.25,仍出現過不少次因為主題原因導致 HTTP 503 的問題。所以便決定藉着這次換域名的機會,把部落格整個重寫一遍了。

3. 現代化的開發體驗#

身為一個前端開發者,我還是希望能用 TypeScript、JSX 這些現代化的工具來開發,而不是傳統的 template engine。

其實我是不怎討好 Hexo 的整套插件系統的,因為他每個插件都需要透過 inject 的方式來運作,不能直接集成到主題內。而 Hexo 在 JS 方面的支援度上也比較欠缺。像是博主經常會在文章內使用 jQuery ,然而 Hexo 並沒有內建這東西,需要自行從 HTML 引入。

4. Hexo 自身的問題#

說到底其實 Hexo 本身也有很多讓我不解的地方。比如它可以直接用 markdown 寫文章,然而早期版本並不支援透過 markdown 語法插入圖片,需要 enable post_asset_folder 然後透過 custom tag 方能插入。

比如我現在要把 image.jpg 放進文章 hello-world 裏,首先要去 _config.yml 裏 enable post_asset_folder

post_asset_folder: true

然後要在 hello-world 需插入的位置新增下面的 custom tag

{% asset_img image.jpg This is the image %}

再把圖片上傳到 post_asset_folder

/blog-root/_posts/hello-world/image.jpg

這樣圖片才能正常的在文章內顯示

然而問題來了,這種方法雖可行,但這也意味着我無法把它們像一般 markdown 那樣在各種 MD 文件編輯器或是平台中正確顯示。對於博主這些要常在文章內插入圖片的人來講可謂不便。

 

為什麼選擇 Astro ?#

說實話其實博主也有考慮過其他 generator,像是 Hugo, Next.js 那些的。但看過網上有指 Hugo 在 Markdown parser 的客製化上限制比較大,且有機會把 $$_ 這些 Latex 會用到的 special characters 漏掉了。而在實測後也發現問題的確存在,故終決定排除在外。

而 Next.js ,我不能說它不香。畢竟它是個基於 React 且同時支援 SSR 和 SSG 兩者的框架。

起初我也有想過把網站遷到 Next.js 去的,但考慮過後發現,就目前的內容而言,SSR 對本站的用處不大。真要是強行遷過去的話反而會覺得有點小題大做了⋯⋯

至於 Astro ,經了解後,發現它是個基於 Vite、能夠生成 MPA 的 SSG 框架。從路徑到各種 Markdown parser 插件等都能夠自訂。簡單來講,只要願意寫 code 基本上沒什麼做不到的東西。

而且大部分的 code 都能透過 TypeScript 完成,有靜態類型的優勢。這點也是深得博主的喜歡。

 

遷移需求#

在開始動工之前,我列出了幾個必須達成的需求:

  • 完全 Static Site - 可以部署到 GitHub Pages、Cloudflare Pages 等平台
  • 即開即食 - 盡量避免主題所需以外的 additional dependencies
  • 效能不能變差 - Lighthouse Performance 桌面版分數要和舊站相若
  • 新舊結合 - 盡可能保留舊站元素,再融合新站特色

 

遷移過程#

重構插件#

正如前文所說,Hexo 有它獨立的插件引入方式。一般來說,需要用其 inject 方式來引入。而通常這類插件都只是含有短短幾句的 JS 代碼。

所以很簡單,只需要把上面 hexo inject 的部份刪掉

hexo.extend.filter.register('before_post_render', function(data){
data.content = data.content.replace(/!{1}\[([^\[\]]*)\]\((.*)\s?(?:".*")?\)/g
// ...
return data;
});

然後略為修飾,把剩下的整段 JS 搬進 ./script 裏面便可以了。

重構 renderer#

好,重頭戲來了。這也是整個重構過程中耗時最長的部分。

還記得上面說過 ShokaX 是一款讓我又愛又恨的主題嗎?沒錯,它自己擁有另一套獨有的 renderer ,其中之一名為 hexo-renderer-multi-next-markdown-it (有好幾個但功能大同小異)。

而他們全都是以 markdown-it parser 來做基底,內含一大堆 markdown-it 插件。然而 Astro 的 markdown 支援並不是靠markdown-it,而是 remark

問題來了, remark 這 parser 主打的是 AST,和markdown-it 完全不同。這意味着單靠直接遷移是完全行不通的。

幸好兩者有一個共通點,那就是它們皆支援 custom plugin,也就是理論上若能把這堆插件全部 rewrite 做 remark plugin 的話,剩下的便好處理了。

於是又上網了解一下 remark 的 plugin 結構,然後請教了 Claude 大師:

芋泥2026-03-02

用 markdown-it 寫的插件能否遷到 remark 去呢

芋泥2026-03-02

我要換 Astro 了但它用不了 markdown it TT

Claude 4.5 Sonnet2026-03–02

這個… 難度有點大呢~

Claude 4.5 Sonnet2026-03–02

畢竟兩者的架構差很遠了…

Claude 4.5 Sonnet2026-03–02

要不您把 repo 發過來我看看再能怎樣搞吧

芋泥2026-03-02

好的 等我一下

就這樣過了兩天

芋泥2026-03-04

hexo-theme-shokax/hexo-renderer-multi-markdown-it hexo-theme-shokax/hexo-renderer-multi-next-markdown-it hexo-theme-shokax/hexo-renderer-aether

芋泥2026-03-04

就是這些了 它們功能上差不多

芋泥2026-03-04

你先替我看看它們是如何工作的 然後順着其邏輯改一改試試

Claude 4.5 Sonnet2026-03–04

好的,這是其中一個經修改後的插件:

隨即試了一下,果然出 error 了

芋泥2026-03-05

出 error 了

芋泥2026-03-05

一堆 unsupported operations

Claude 4.5 Sonnet2026-03–05

看來還是得重構了…

Claude 4.5 Sonnet2026-03–05

這個要 patch 的話有點難

然後因為要段考了,又過了一個星期

芋泥2026-03-13

我研究了 發現它是走 AST 路線的

芋泥2026-03-13

要搞清楚 mdast 才可以開弄

芋泥2026-03-13

你先想一想它們在 mdast 的 syntax 和 logic 下該如何處理 然後再和我一起重構

Claude 4.5 Sonnet2026-03–13

好的。那 css 的部分你希望該怎樣?

芋泥2026-03-13

盡量保留吧 有需要的才考慮 refactor

於是便開展了一場大型的重構激戰,過了幾天

芋泥2026-03-19

render 出來了

芋泥2026-03-19

目前版面沒什麼問題

芋泥2026-03-19

嗚嗚嗚終於搞好了 TT…

Claude 4.5 Sonnet2026-03–19

恭喜!也真是辛苦你了…

然而你們以為這樣便結束了?非也…

你們上面看到的 chatbox ,那個並不是原本 renderer 內有的,而是借鍳某位大佬的 source code 改造而成的。

其實那個是博主還在用 Hexo 的時候便一直以來都想要的東西,只是在 Hexo 和 markdown-it 的架構下要放這種東西的話幾乎不可能。所以便趁着這次重構的機會一同把它帶進來了。

只不過,要處理那東西,可謂又是一場惡戰。礙於篇幅所限,這個留在下一篇文再說。

全自動化 deploy#

以前用 Hexo 每當要出新文章時,都要先把文章 git push 到 repo 上,然後再另外從 server 上面 git pull 以取得最新變更 (除非你只是打算透過 hexo-deploy-git 把網頁 deploy 到 GitHub Pages 上)。某程度上或會顯得有些不便。

所以這也是本次重構重點更新的項目之一,那就是自動在我 push to repo 後直接從 server 上面 git pull, 在毋須任何手動操作的情況下全自動更新網站。

𤔡此我在目標 server 上安裝了 GitHub self-hosted runner,透過 SSH 來登入 GitHub,再把其連接到 github-actions 裏,隨後便寫出了下面這段 YAML 代碼:

name: Deploy to Production Server
on:
push:
branches: [ master ]
workflow_dispatch:
repository_dispatch:
types: [ content-updated ] # Must match event-type from trigger
jobs:
build-and-deploy:
runs-on: self-hosted
defaults:
run:
working-directory: /home/neko/srv/blog/Mizuki-Revanced
steps:
- name: SSH Pre-Test
run: |
whoami
ssh -T git@github.com || true
- name: Pull latest changes
run: |
git fetch origin master
git reset --hard origin/master
git submodule update --init --recursive
- name: Install dependencies
run: pnpm install --no-frozen-lockfile
- name: Clean cache and backup dist, without removing the dist folder itself
run: |
rm -rf .astro node_modules/.astro
if [ -d "dist" ]; then
rm -rf dist_backup
cp -r dist dist_backup
rm -rf dist/*
echo "Existing dist backed up to dist_backup."
fi
- name: Build site with no caches
id: build
run: pnpm run build --force
env:
ENABLE_CONTENT_SYNC: true
CONTENT_REPO_URL: ${{ secrets.CONTENT_REPO_URL }}
USE_SUBMODULE: true
- name: Cleanup backup on success
if: success()
run: |
rm -rf dist_backup
echo "Build succeeded, backup removed."
- name: Restore backup on failure
if: failure()
run: |
rm -rf dist/*
if [ -d "dist_backup" ]; then
mv dist_backup/* dist/
rm -rf dist_backup
echo "ERROR: pnpm build failed, usually this is due to a syntax error in the markdown files."
echo "For safety, the previous dist has been restored. Please check the build logs for details."
fi
exit 1
# Our Docker Container will automatically serve the lastest files in real time.

簡單來說整個流程如下:

flowchart TD A[/"Trigger Event"/] A1["Push to master"] A2["Manual dispatch"] A3["Repository dispatch\n(content-updated)"] A1 --> A A2 --> A A3 --> A A --> B["Runner: self-hosted"] B --> C["SSH Pre-Test"] C --> D["Pull Latest Changes from Content Repository"] D --> E["Install dependencies"] E --> F["Clean Cache & Backup dist\nrm -rf .astro node_modules/.astro"] F --> G{"dist/ exists?"} G -- Yes --> H["cp -r dist dist_backup\nrm -rf dist/*"] G -- No --> I["Skip backup"] H --> J["Build Site"] I --> J J --> K{"Build\nSucceeded?"} K -- "Success" --> L["Cleanup Backup\nrm -rf dist_backup"] L --> M["Docker serves latest dist/ automatically"] K -- "Failure" --> N["Restore Backup\nrm -rf dist/*\nmv dist_backup/* dist/"] N --> O["Exit with error code 1\nPrevious dist restored"]

Collection#

Astro 中有個 collection 機制可以用來管理類似性質的一些資料,例如 blog post 這種同質性很高的東西。

目前的話在格式方面的變化,其實只是從原本 Hexo 的

source/_posts
├── post-1
│ ├── index.md
│ └── cover.png
├── post-2
│ ├── index.md
│ └── cover.png

變成了這樣

src/content/posts
├── post-1
│ ├── index.md
│ └── cover.png
├── post-2
│ ├── index.md
│ └── cover.png

這個設計也解決了我在 Hexo 的一個痛點:Markdown 中引用圖片的 relative path 問題。現在圖片就放在文章旁邊,編輯器的 preview 也能正常顯示了。

主題#

主題部分我是選擇使用 Mizuki 作為基礎,然後再根據自己的需求做調整。Astro 的 component 架構讓 customization 變得非常容易,基本上就像寫 React/Vue 一樣。

其他功能#

其他像是 RSS、Sitemap、搜尋功能等,Astro 的生態系都有現成的 integration 可以使用,整合起來也是非常順利。

(待續)

分享

如果這篇文章對你有幫助,歡迎分享給更多人!

部落格搬家啦!一次從 Hexo 到 Astro 的大遷徙記~
https://moe.lolicon.io/posts/blog-deployments/migrating-from-hexo-to-astro/
作者
ホシノ ゆき
發布於
2026-03-01
授權條款
CC BY-NC-SA 4.0

部分資訊可能已經過時

目錄