blog.ryota-ka.me

ブログを自作して移転した

2014年からおよそ7年半に渡って使い続けていたはてなブログを離れ,自作のブログシステムに棲家を移すことにした.

2022年の年明けに手を付け始め,休日や退勤後の時間を用いて作業を行ったところ,およそ半月後の1月半ばには移行を完了させることができた.

この記事では,移行に際してブログシステムを自作することに決めた動機や,実装の骨子について紹介する.

なぜ自前で作るのか#

はてなブログを離れるのはいいとしても,なぜブログシステムを自作するという選択肢を取ることになるのだろうか.単に日本語圏の開発者たちの人目に触れることを狙うならば,QiitaZenn といったプラットフォームに投稿する方が有利ではないか.

しかし,特定のプラットフォームにコミットすることは,すなわち様々なリスクも同時に引き受けることである.過去に執筆した記事がガイドラインに違反されていると判断されれば,運営者の一存で,事前の予告なく非公開にされてしまうかもしれない.あるいは,ある日突然,自らの閲覧履歴を開示され,アカウントを削除する必要性に迫られるかもしれない.杞憂だと言う人もいるだろう.もちろん,起こるとは限らない未来である.しかし,起こらないとも限らない未来でもある.自分の資産は自分で守る必要がある.

こうしたリスクを念頭に置くと,特定のプラットフォームへの依存は回避したい.これはホスティングに限ったことではなく,コンテンツについても同様である.各プラットフォームにおける Markdown への独自拡張は便利ではあるが,ヴェンダー・ロックインの直接的な原因になりうる.

また,インターネット上にリソースを作成する営みである以上,パーマリンクにまつわる問題も生じる.仮に将来再度の移転を計画せざるを得なくなったとして,新たな移転先に対しては301リダイレクトを設定したい.しかしながら,そのような設定を行える親切なプラットフォームの存在は寡聞にして耳にしたことがない.一方,独自ドメインでホスティングしておけばこのような問題に悩まされることはない.

このブログの実装について#

「ブログシステムの自作」とは具体的に何をすることなのか.最も簡単な答えは「Markdown を HTML に変換すること」である.これだけであれば Pandoc でも実現できるし,そうでなくても GitHub に push しさえすれば,それなりに整った見た目で表示してくれる.この記事は以下のページにおいても概ね問題なく読むことができるだろう.

しかし,実用に耐えるブログとしての役割を果たすためには,リンクのプレヴュー,数式のレンダリング,RSS フィードといったような機能も兼ね備えている必要がある.

初めは「Next.js でブログくらい簡単に作れるのだろうか」と思って調べてみたところ,@next/mdx というプラグインの存在を知った.これは Next.js の page として MDX 形式のファイルを利用可能にするものである.

筆者としては MDX が将来的に広く普及するとは考えていないため,とりあえずはプレーンな Markdown ファイルを pages/ ディレクトリ以下に配置することを試してみた.しかし,プロトタイピングを進めるに従って,拡張性に乏しいという感覚を徐々に抱くようになった.また,ブログリポジトリの本質たる Markdown ファイルの配置先を,採用するフレームワークの都合によって決めるといった行為は望ましくないとも感じ,採用を見送った.

さて,「Markdown を HTML に変換する」とは,もう少し突き詰めて考えると,

  • Markdown で記述されたテクストを Markdown の AST にパーズする
  • Markdown の AST を操作する
  • Markdown の AST を HTML の AST に変換する
  • HTML の AST を操作する
  • HTML の AST を HTML で記述されたテクストに変換する

くらいのところを指している.実は,MDX が実装の下敷きとして用いている Unified というエコシステムは,まさにそのような仕事を行うために作られている.

Unified について#

Unified エコシステムでは,Markdown および HTML の AST として,それぞれ mdast および hast が定義されている.また,それらを取り扱うプラグインエコシステムとして,それぞれ remark および rehype が提供されている.例えば,文字列を mdast 形式 の AST にパーズする remark-parse,mdast 形式の AST を hast 形式の AST に変換する remark-rehype,hast 形式の AST を文字列に変換する rehype-stringify などがそれにあたる.もちろん,必要であれば自分でプラグインを書くこともできるし,そのためのユーティリティ関数も多数用意されている.このように,小さな部品を組み合わせることで大きな問題を包括的に解こうとする思想は,私の嗜好に適っている.

以下では Unified エコシステムの上に実装した機能の一部を紹介する.

数式のレンダリング#

$formula$ という記法*1で数式を記述することができる.また $$ で display mode も利用することもできる.これらの挙動は remark-math および rehype-katex により実現されている.レンダリングは KaTeX を用いてなされる.

リンクのプレヴュー#

この記事中にも既に何度か登場しているが,リンクのプレヴューを表示することができる.URL が単一の段落を構成する場合,ビルド時に当該 URL にリクエストを送信し,<head> 要素内のメタ情報を取り出している.細かいテクニックだが,このようなリンクを <a> 要素としてレンダリングしてしまうと,通常のアンカーと区別が付かなくなり,取り扱いが難しくなるので,<open-graph-card> という custom element を表すノードに変換するようにしている.

終わりに#

今回が移行後初めての記事となったが,ブラウザ上のエディタではなく,普段から使っているエディタで編集ができる点において非常に快適であると感じた.Markdown やコードスニペットのフォーマットなどは手作業で行うにはあまりに退屈な作業だ.ここの部分の体験を抑えている点においてやはり Zenn はよく考えられていると思わされる.

もちろん,盛り付けのための食器をいくら用意しても,肝心の料理がなければ意味をなさない.昨年はインプットに偏重し,アウトプットを怠ったという感があった.これを契機として,今年はより多くの発信を心掛けていきたい.

このブログの実装は以下の GitHub リポジトリで公開されている.足りない機能はまだまだあるが,気の向いたときに少しずつ育てていけるのも自作の醍醐味である.

補遺I. はてなブログが not for me だったところ#

念のため初めに断っておくが,これは単に「筆者がはてなブログのターゲットユーザではなかった」という話でしかなく,あなたがはてなブログを利用すべきでないという主張ではない.そもそもはてなブログの開発ティームとしても,ソフトウェアエンジニアを主要なターゲットユーザとして想定しているわけではないだろう.

TeX#

はてなブログでは [tex:formula] という記法*2を用いて,記事中に TeX\TeX の数式を埋め込むことができる.埋め込んだ数式は MathJax によってレンダリングされる.

この機能自体は非常に便利なのだが,「Markdownモード」*3との相性がとにかく悪い.Markdown パーザの都合で,特定の記号を記述する際にバックスラッシュによるエスケープを要求されるからだ.

https://twitter.com/ryotakameoka/status/1046085406884855808

例えば,bab^a と書くためには,[tex:b^a] ではなく [tex:b\^a] と,AiA_i と書くためには [tex:A_i] ではなく [tex:A\_i] と,それぞれ記述する必要がある.

https://twitter.com/ryotakameoka/status/1091971993564372992 https://twitter.com/ryotakameoka/status/1218869187566657541

また,[ ⁣[] ⁣][\![\cdot]\!]と表示するためには [tex:[\![\cdot]\!]] ではなく [tex:\\[\\!\\[\cdot\\]\\!\\] と記述しなければならない.

https://twitter.com/ryotakameoka/status/1280577338237837312

このように,特定のプラットフォームに固有の記法を強いられると,執筆体験が損なわれるのみならず,記述した文章自体の可搬性を低下させてしまう.

加えて,MathJax のレンダリングはそれなりに遅い.

脚注の中で inline code 記法が使えない#

((foobar)) という形式で脚注を記述することができる*4が,この中に Markdown の inline code 記法を含めることができない.こちらも Markdown パーザの都合なのかどうかはわからないが,ともかくこのような制約は,技術的な記事を書く際にしばしば足枷になる.

一部言語の syntax highlighting が効かない#

コードブロック記法を用いる際,言語を指定することで syntax highlighting を有効にすることができるが,残念ながら2022年1月現在サポートされるファイルタイプの一覧に Nix が含まれていない.プラットフォーム側としてはわざわざマイナー言語への対応を加えるインセンティヴはほとんど無いと思うが,筆者が近年 Nix に関する記事を多く書いていることを考えると,失われる読者体験は無視できないものであった.

見た目のカスタマイズの限界#

はてなブログではカスタム CSS を記述できるため,フォントやレイアウトを調整したり,prefers-color-scheme media query (いわゆる dark mode) に対応させたりしていた.しかし,こうした対応にも限界がある.そもそも市井のブログサーヴィスは,多数のユーザのユースケースに対応するため,豊富な機能を有している.完璧にやろうとすれば,自分が存在すら把握していないページに対しても綺麗にスタイルが当たるよう,カスタム CSS を記述しなければならない.これはなかなかに不毛な作業である.

はてなキーワード#

そもそもはてな市民以外にニーズがあるのかどうかよくわからない機能だが,文中にリンクが埋め込まれるだけならまだしも,TeX\TeX のレンダリングに干渉する場合がある.

https://twitter.com/ryotakameoka/status/1048988784224661504

上記の問題の原因は,はてなキーワードに "bot" というページが存在し,そこへのリンクが埋め込まれたことだった.原因が判明した瞬間には渋い気持ちにならざるを得なかった.

https://twitter.com/ryotakameoka/status/1091974033388662784

プレヴューの体験の悪さ#

記事編集時のプレヴューがとにかく遅い.また,「Markdown を書き換えた際にプレヴューが更新される」という体験が実現できないため,インクリメンタルな執筆や推敲が不可能であり,非常に苦労する.前述の TeX\TeX の問題が正しく work around できてるかを確かめるためには,エスケープしきれていない箇所を探し,バックスラッシュを挿入し,プレヴューモードに切り替えて数秒から10秒程度待ち,ページをスクロールしてレンダリングされた数式を探し,表示結果が正しいかどうかを確認し,必要であれば再度修正する,というフローを何度も何度も繰り返す必要がある.こうしたフィードバックサイクルが低速でしか行えないのは大きなストレスだった.

広告#

これは無料で使わせてもらっている以上仕方がないが,広告によって生じる layout shift を見るとやるせない気持ちになる.もちろん有料プランでは非表示にできるが,月に1,000円程度払うよりは「はてなブログをやめる」という選択肢を取ることになってしまう.

補遺II. 移行に際してはてなブログがよかったところ#

記事の内容がだいたい Markdown だった#

脚注や TeX\TeX,プレヴュー付きリンクなど,いくつか互換性がない記法はあるものの,過去に書かれた記事は基本的には Markdown で記述されており,多少の努力を払えば移行することができた.もちろん,仮にはてな記法で記事を書いてきていたならば目も当てられない状況になっていただろう.

<head> 要素に任意の内容を記述できる#

「上級者向け」かつ「開発者向け」と謳われてる設定項目として,<head> 要素内にメタデータやスクリプトなどの任意の要素を記述できる機能が提供されている.こうした高度な設定が利用できることで,移行先ページへの遷移などを実現することができた.

脚注#

*1: このように,Markdown への拡張をどの程度許すかという選択は可搬性の問題に直結するが,とはいえ CommonMark に準拠して記事を書くことは,表現力の問題から叶わないだろう.結局の所,その記法が世界中のコミュニティにおいてどの程度支持されているかを拠り所に決めるほかない.$$$ を用いた TeX の記述は,Pandoc の tex_math_dollars 拡張によってサポートされている.

*2: これははてな記法の一部で,「tex記法」と呼ばれるものであるらしい

*3: 記事を Markdown 形式で書けるモード.他には WYSIWYG エディタで編集できる「見たままモード」や,昔ながらのはてな市民以外に使われているのか疑問に思う「はてな記法モード」があるが,大方の Web 系ソフトウェアエンジニアは Markdown モードを利用していると想像する.

*4: これもはてな記法の一部で,「脚注記法」というのだそうだ.