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: これもはてな記法の一部で,「脚注記法」というのだそうだ.