このブログをHugoからAstroに移行した
2015年にTumblrからHugoに移行し、2021年にSurge.shからCloudflare Pagesに載せ替え、それ以来ずっとその構成で記事を書いてきたこのブログを、今回Astro + Cloudflare Workersに移行した。記事を見る分にはなにも変わっていないはずだ。URLも同じままになっている。
なんで
Hugoに致命的な不満があったわけではない。ビルドは速いし安定しているし、なにより慣れていたこともあってずいぶん長いこと放置していた。たまに気になったところ、たいていはスタイルシートをわずかに更新するくらいでそれ以外をいじることはほとんどなかった。基本的にHugoの流儀に則っていたものの、Hugo流のカスタマイズはいくつも実装していた。しかし振り返ってみるとそれが結構ストレスとなっていたのである。
まずGo Templateというのは本当に書きづらい。Hugoのテンプレート言語はGoのhtml/templateベースで、慣れればまぁ読めるんだけど記法がいちいち独特で見通しが悪い。テンプレートを大きくいじる機会は年に数回あるかないかなので、そのたびに文法を調べ直すことになる。VS Codeでもまともにハイライトされないのも長いこと気になっている点で、もう少し素直な言語でテンプレートを書きたかった。
あとはエコシステムの問題がある。Hugoは単一バイナリで完結するという思想のソフトウェアであるため、なにか追加の機能がほしいときは外部ツールと組み合わせるか、Hugo本体にその機能が取り込まれるのを待つしかない。最小限のブログなので大した機能は必要としないけれど、しかしWeb開発でつい使いたくなるようなCSSのビルドなんかでは結局Node.jsのエコシステムにも相乗りしないといけないとなると、結局Hugoに全然完結しておらず中途半端な状態だった。
Cloudflare Pages
そしてこのブログのデプロイ先であるCloudflare Pagesも現在はなんとなく不穏な空気がある。Cloudflareから公式に、PagesからWorkersへの移行ガイドが用意されていて課金体系も同じように設計されている。PagesはもはやWorkersの完全なサブセットになっていて、プロダクトとしてはメンテナンスモードに近い形として放置されそうな雰囲気が色濃い。
なぜAstroか
静的サイトジェネレーターは山ほどある。最終出力は単なるHTMLとCSSなので、なんでもいいといえばなんでもいい。でも数年使い続けることを考えると、活発にメンテナンスされていて、かつ自分にとって書きやすいものを選びたい。
Astroはコンポーネントの記法がHTML+JSのシンプルな構造で、ReactやVueといったフレームワークに依存していない。テンプレート内でJavaScript/TypeScriptがそのまま書けるので、Go Templateと比べたらその親和性は非常に高く快適だ。Content Collectionsという仕組みで記事の型定義やバリデーションもできる。npmのエコシステムに乗っているのでRSS生成やサイトマップなんかもAstroの各パッケージを入れるだけで済むという手軽さと、JSで作られているわりにはHugoに比べても十分高速にビルドできるのもうれしい。
移行作業
できるだけAstroの流儀に従いつつ、これまでのブログとURL構造も出力されるHTMLも変化しないようにDiffをとりつつほとんどClaude Codeに丸投げで完了してしまった。
コンテンツ形式の変更
これまでのMarkdownからMarkdocに変えた。MDXという選択肢もあったけど、React非依存でCommonMarkのスーパーセットであるMarkdocのほうがポータブルで好みだった。独自の記法は{% %}で囲む形式で、これはHugoのショートコードとだいたい同じように使えるのでこれまでの記事の拡張子を変えるだけでもほとんど同じように使える。
このブログで唯一使っていた自作ショートコードvideoは287箇所あったが、Hugoの{{< video src="..." >}}をMarkdocの{% video src="..." /%}に正規表現で一括変換した。記事中の生HTMLはMarkdocのallowHTMLオプションでそのまま素通しさせている。
ディレクトリ構造のフラット化
Hugoではcontent/post/2024/1705809398.mdのように年別ディレクトリに記事を分けていたが、Astroではcontent/post/1705809398.mdocとフラットに並べることにした。年の情報はフロントマターのdateから取れるので、特に意味のないフォルダ分けではあったのだが、このブログの初期段階ではファイルの整理方法として年別にわけて管理していた名残として残っていた。Astroはフォルダ構造をそのままビルド後のURL構造として再現するため、フラット化したことでファイル名がそのままURLのIDになりURL構造とフォルダ構造が一致させられて気持ちが良い。
URLに使っているファイル名はランダムな数字となるようにした。これはこのブログの最初期にTumblrというマイクロブログサービスをつかっていて、それの記事IDをそのまま自分のブログに流用し、Tumblrからの移行後はとくに何も考えずにファイル作成時のUNIX timeが採番されるようにしていた。この仕組みも別に困っていたわけではないものの、投稿日とファイル作成日はわたしの場合数年単位でずれることもあるので、いっそ完全にランダムな値であるということが明確になるように重複しない単なる乱数がファイル名となるようにした。UNIX timeを使うとファイル名に意味が生まれてしまう。Goのmapのiteration orderが意図的に安定しないよう実装されているように、記事IDにも意味がないことを明確にしたかった。傍から見れば本当にどうでもいいところである。
最終的にHugoとAstroのビルド出力をdiffして、全472記事のURLとテキスト内容が一致することを確認した。HTML構造にはいくつか差分が出たけれど、いずれもHugo側のinvalid HTMLを修正した結果の意図的な差分だった。たとえばHugoは<main>の中に<main>をネストしていたり、<p>の中に<figure>を入れていたりしたが、まぁこれはHugoが悪いというよりは自分のテンプレートの書き方が雑だったのだろう。Astroに書き直す際にこのあたりはすべてきれいにし、常にvalidなHTMLが生成されるように修正した。
おわり
WordPress → nanoc → Tumblr → Hugo → Astroと、これで4回目のブログエンジン移行だ。Hugoは10年以上使っているのでここだけ相当長かった。マイグレーションしなきゃいけないほど大切なものかというと、まぁそれはどうだろうという内容ではあるのだけど、しかしほそぼそと15年も続けているとそれなりに大切に感じてしまうものなのだ。