コンテンツへスキップ
ものがたり
戻る

はてなブログから完全移行するためのAstroブログ環境の構築

最近になってようやく時間の余裕ができたので、しばらく前から計画していたはてなブログからの脱出作業にようやく着手できるようになった。

https://bsky.app/profile/atsushieno.bsky.social/post/3mkovonfov22b

新しいURLは https://monogatari.audiopluginlab.com になる。新しいドメインで読んでいる人にとっては自明だし、このエントリーははてなブログの最後の投稿になる予定なので、そっちにとっては引っ越し先の告知ということになる。

ブログ程度ですらFirefoxのStrict設定でまともにCSSが取得できないサイトは、もう「終わっている」と判断するのが適切だろう。問題がずっと解決されずに放置されていたので、はてなブログやはてなブックマークに手を入れられる技術者はもうはてなには残っていないのだろうと理解している(何が壊れるかわからないので手を入れられない)。他に自由な代替ソフトウェアがいくらでもあるのに、もうメンテされていないサイトをいつまでも使い続けるというのは、技術者としてはあまり健全ではない。

今回は、2004年にはてなダイアリーに移行してから、はてなダイアリー管理部分とはてなブログ管理部分を合わせて過去22年分のブログ投稿(!)を、全てAstroの静的ページ構成のブログシステムに置き換えた。このエントリーも新しいシステムの上で書いている。置換作業はほとんどAIへの指示出しのみで、自分はAstroやAstro-Paperの詳細には深入りしないままに終わっている。

一応これを公開できるところまで進めることができたので、同様にはてなから脱出しようと考えている人の参考になればと思って詳しくまとめることにした。

前提ブログ執筆環境

はてなブログから脱出するためには、まずはてなブログのエディタを使わない執筆環境に身を置いている必要がある。

ここ数年の間、自分はmarkdownでしかはてなブログを書いておらず、投稿頻度も月に1〜2回で(ただしはてなと別にzenn アカウントにも投稿しているし、別途英語ブログもgithub.ioで出している)、その編集にははてなのページではなくStackEditのインスタンスを使っている。Google Driveと同期して、(頻繁に機能しないページ内ダイアログを出してくることもあるのは鬱陶しいところもあるものの)ちゃんと必要な「どのPCからでも編集できる」という機能を提供してくれる。

StackEditに限らず、この種のmarkdownエディタの問題として「画像のストレージが無い」という問題があるのだけど、近年はimgurに上げてその画像リンクを使うようにしている。20年前ならflickrなどを使っていたことだろうが、2026年に画像を直リンできるサイトは限られている(GitHubなどもできない。その画像URLは手元では見えていても他者には見えないやつだ)。はてなブログのエディタだと、f.hatena.ne.jpのフォトライフ記法を使い、はてなに骨を埋めることになってしまう(とはいえ画像は後述するように取り出せるので、移行できないことはない)。

もっとも、imgurにも固有の問題があって、たとえば英国からのアクセスは年齢認証できない限り完全にブロックされてしまう。旅行中に画像にアクセスできずに開発作業(ブログ執筆ではなく)が止まったことがあった。とはいえ、日本で暮らしている範囲では無理無く使える。メリット・デメリットを比較してimgurに落ち着いていた。

外部リンクに関しては、URLだけの段落を作っておいて、書き上げた後、最終的にはてなに投稿する際にOGPに置き換えていた。具体的にははてなブログのエディター上でURLだけの行を削除して貼り付け直すだけだ。この時に埋め込み方法を問い合わせるダイアログが出てくるので、通常はOGPの埋め込みを選択していた。

これ以外は特別な作業はしていない。通常は投稿した後にMastodon (現在はFedibird) とBlueskyにコメントを付けて(手作業で)URLを投稿している。

Astro-Paperの選択

どのような環境に移行するかは無限に悩むところだったのだけど、死守する条件は「静的ページとしてデプロイできること」だけにした。もちろんvercelなどにデプロイして動かせるものもあるだろうけど、SSG (static site generator)1 にしておけばgitリポジトリ管理が全てになるというのは大きい。

そういうわけで、今回はAstroを使うことにした。もう少し前ならEleventyなどを使っていたかもしれないが、2026年現在、同様にJavaScript依存関係ゼロで構築できるSSGであれば、サイトのテンプレートがいろいろ揃っているほうが始めやすい。Astroは4月にandroidaudioplugin.orgを刷新した時にも使っているので、ごく大まかには把握している(ただこのサイトもほぼ内容が無くAI slopに近い)。

Astroのサイトにはコミュニティが開発したサイトのテンプレートを探せるページがあるので、その中でFreeかつBlogをターゲットにしたものをしばらく探して回ったのだけど、Freeの多くはPaidの機能ダウングレード版が多く、それらを使うとあまり機能追加が前向きに見込めないのと、このサイトで探しても最新登録テーマが先に出てくる感じでどこまで一般的なものか判断に困ったのとで、どうせ大半がGitHubにあるならそっちで探そう…となった。

今回採用したAstro-Paperは比較的地味だけど、他だと人気のfuwariなどと比べると、一応新しいAstro 6.4系列を使っていて、メンテもされている。Astro 7.0はまだリリースされたばかりなので、移行できていないものが多いだろう。この辺りは後々差し替える可能性も十分ある。最悪、他のリポジトリとサブドメインに移行したうえで、こっちは転送リンクを生成するようにしてしまえば、Permalinkが失われることにはならないだろう。

作業方針とソース(コード・データ)の保持指針

今の自分にとって、AstroやAstro-Paperは、その使い方に詳しくなる技術的意義を特に感じられなかったので、ほぼAI任せになっている。ただ「Astroに移行するからこのエクスポートデータを変換して」だけでは十分に移行できているとは言い難いので、その後にさまざまな加工を施すことになった。

基本方針として、本家の更新になるべく簡単に追従できるように、なるべく既存のファイルには手を加えず、はてなからエクスポートしたファイルにも手を加えずに、新規ファイルの追加とAstroによるファイル生成時点で動的な書き換えを行う方向性で進めている。pnpm run buildpnpm devで、はてなのエクスポートデータをAstroのpostsにコンバートして、それをAstro-Paperのやり方で最終的な出力に変換させている。

はてなから脱出するのは「はてなブログやフォトライフなど関連エコシステムはもう長く保たないだろう」という想定があるためなので、たとえばフォトライフにある画像をそのままURLとして残しておいたら、はてなが消滅した時に共倒れになってしまう。そのため、はてなに上げてあった画像などのリソースは全て引き上げてリポジトリ上に保存してある。ちなみにはてなフォトライフにはなぜかはてなブログと異なりエクスポートが用意されていないので、個別に全てダウンロードする必要がある。この辺は今どきなら専用ダウンロードツールが無くてもAIが勝手にHTTP(S)で取得してくれる。

Astro-Paperから変更している部分

はてなフォトライフ画像の変換

f.hatena.ne.jp 上にアップロードした画像は全てリポジトリのローカルファイルとして保存し、デプロイ先にもアップロードされている。ブログエントリではリンク先を差し替えてある。フォトライフ上の画像を参照していた部分はダウンロードされた画像のエイリアスにマッピングされていて、サイトのビルドの時点でもうフォトライフは消えていても困らない状態になっている。

OGPの生成

前半でも書いた通り、はてなブログでは、ブログエディター上でリンクのURLをペーストすると、それを表示する方法をユーザーに問い合わせて、OGP画像などに変換できる仕組みを提供している。ブログエディター上では[URL:embed:cite]のように書き換えられるが、エクスポートデータはそのまま出力されるわけではなく、MovableType形式(!)になっていて、この部分はiframeに変換される(適宜改行を入れた):

<p>
  <iframe
    src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fatsushieno%2Fuapmd-kmp"
    title="GitHub - atsushieno/uapmd-kmp: Kotlin bindings and CMP porting experiment for uapmd - not working due to plugin UI hosting"
    class="embed-card embed-webcard" scrolling="no" frameborder="0"
    style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"
    loading="lazy">
  </iframe>
  <cite class="hatena-citation">
    <a href="https://github.com/atsushieno/uapmd-kmp">github.com</a>
  </cite>
</p>

自分のはてなブログでは、OGPで表示したい場合はURLだけの行を作って、それをはてなのエディター上で置き換えていた(あるいは直接[URL:embed:cite]を書いていた)ので、こちらでもmarkdown中に単一行で書かれたURLはOGPに変換する処理を追加した(させた)。

OGPになる部分は、CORSの制約上、エンドユーザーの環境で動的に生成することができない。このブログで必要になるOGPの件数は100件強だったので、Codexが「URLに対応する各サイト生成済みの画像URLにマッピングした情報をリポジトリに残し、ページ生成時にここから画像URLを解決する」アプローチで解決した。画像そのものは各サイトが提供していればそれを使い、無ければデフォルトでページタイトル等から適当に生成される。

YouTubeやSpeakerDeckの埋め込みコンテンツ対応

YouTubeやSpeakerDeckは、iframeを使って動画をその場で再生したりスライドをその場でめくって見られるように作られていて、はてなブログはそれらを展開するように作られている。

実装のアプローチをあまりサイトの具体的な列挙で分岐してもらいたくはなかったのだけど、生成されたコードを見てみると現状では限定列挙になっていて、さらにBluesky投稿には個別にフィルター関数が存在していたりと、あまり美しくは整理されなかった。

年月単位のアーカイブ

Astro-Paperのデフォルト設定では、アーカイブページはフラットに作られていて、全てが1つのページに展開されることになる。今回インポートしたブログは過去22年分、600本近いエントリーがあって2、そのままリストになっていても目的のページを探し出せない。少なくともはてなブログと同様に年月単位で一覧できるようになっていてほしい。そういうわけでこれはアーカイブページに手を加えて全投稿の一覧とは別に生成してもらうようにした。投稿ファイルは年月日でフォルダ分けされることになった。

日単位のエントリー

今となっては考えられないが、昔はブログがSNS投稿の代わりになっていたので、1日に何本もエントリーを書くことが多かった(これは自分が異常者だというわけではない。あるいは自分だけが異常者だというわけではない)。

この頃のはてなダイアリー(はてなブログではない)は、データの管理がまだ雑だった。エントリーにはコメント(はてなブックマークコメントではない)を書くことが出来たが、コメントは日単位のページに付けられていた。あるいは日単位のページに付けることができた。コメントが個別のエントリーに付くようになったのはおそらく内部設計がはてなブログになってからだ。

コメントはエクスポートデータに含まれていて、今回インポートしたページにもそのまま全文残されている。これは当然自分の著作物ではないので、本来は除外すべきものではあるが、さすがにほぼ権利侵害の要求を受けることもなく、財産的価値も寡少であろうと考えて、そのまま表示している(当然著作権者からの請求があれば削除等で対応させていただく)。

現在はてなでデプロイされているはてなブログには、はてなダイアリー時代に「日単位で」付けられたコメントを表示できないというバグがある。たとえば2004年9月9日のエントリーにはコメントがあるのだが、これは(いつ頃だったか)はてなダイアリーから強制的に変換されたはてなブログの同日のページには残されていない。エクスポートはされるのに。

どのように変換すべきか悩んだけど、最終的に「従来型の投稿で、複数エントリーが公開された日については、日単位で見られるアーカイブページを作る」というアプローチで対応した。同じ文章が2エントリー分表示されるので鬱陶しいところはあるけど、見た時に「昔のまま」っぽいのはこっちのほうだ。一定の時期からこのページは生成されなくなる。はてなダイアリーからはてなブログに引っ越してからはこの問題はなく、そもそも近年では1日に2件も投稿することはなく、ほぼ無用の懸念だ。

新しいエントリーのエディター

このエントリーは、今回は頻繁にこのブログの実装をいじらないといろいろ調べられないのでVSCodiumで書いているが、通常業務としては引き続きStackEditで原稿を書くことになるだろう。最近は同人誌の類も全部StackEditを使ってpandocで書いているので、手に馴染んでいて、現状特に変える理由が無い。

公開するときには、他のエントリーからslugをコピペして内容を適当に調整してpnpm devで確認すれば終わりだ。この辺は既存のGitHub Pagesを使ったブログと変わらない。

はてなのほうに残っているデータを消すかどうかは、今後のはてなの動き次第だ。Twitterのデータは悪用されないよう全て消したが、はてなでも同じことをする必要があるかもしれない。

デプロイ先の選択

今回GitリポジトリとWebサイト発行はGitHub Pagesに妥協して使っている。理想をいえばTangledにリポジトリを作って公開しても良かったのだけど、Tangledでは非公開リポジトリを扱えないため、自分のようにリポジトリを公開しても問題ない勢にしか参考にならない可能性が高いと思って、今回はGitHub Pagesにした。

Tangledでプライベートリポジトリを使えるようになるためには、まずTangledが前提としているATProtocolがPermissioned Dataをサポートできるようになる必要があり、これはBlueskyのATProto開発チームがまだ現在進行形で作業しているところだ。それが完成しないとTangledは先に進めないだろう。

GitHub PagesのURLがpermalinkにならないように、冒頭のbskyの投稿時期に取得しておいた audiopluginlab.com のサブドメインを使っているので、いずれTangledでデプロイできるようになったら、そっちに切り替えるだろう。

この移行プロジェクトの再利用可能性

このリポジトリ自体はgithubで公開状態にしておくので、これが「自分もはてなブログから脱出したい」という人々の参考になると良いと思っている。

Footnotes

  1. 今回はstatic site generatorをこう呼ぶことにする(server side generatorやsoftware-controlled sound generatorではなく)

  2. 近年ではそんなに熱心に書いていないが、昔はSNSも無かったのでブログが落書きの場になっていたわけだ


この記事を共有:

次の記事
5月の開発記録 (2026)