Mix Spaceで記事やノートを管理していて、そこに軽くて速いサイトをHexoで用意したいなら、Hexoのリポジトリから定期的にMix Space APIを引いて内容を取り込み、自動でビルドしてGitHub Pagesにデプロイする、という手がある。
全体の流れ
ワークフローは8時間ごとに動き、push や手動操作でも起動できる。実行のたびにオンラインの公開コンテンツを読み直し、その時点の記事ディレクトリをまるごと生成してから、Hexoのビルドを走らせる。
図にある「数字の割り当て・再利用」は、適当に番号を振っているわけではなく、マッピング表を使って行っている。まずはそういうものだと頭に入れておいてほしい。詳しくは後で説明する。
なぜAPIから全件同期するのか
新しい記事を足していくだけの方が簡単そうに見えるが、削除・非公開・修正にうまく対応できない。今の実装は毎回、記事とノートの一覧をすべて読み込んでいる。
GET /api/v3/posts?page=1&size=50&truncate=1
GET /api/v3/notes?page=1&size=50&withSummary=1
一覧APIがページング情報を返したら、page === total_pages になるまでリクエストを続ける。一覧は記事の識別情報と要約を得るためだけに使い、そのあと各項目の詳細を取りに行く。
GET /api/v3/posts/{id}
GET /api/v3/notes/nid/{nid}?single=1
こうしてタイトル、本文、タグ、カテゴリ、作成日時、更新日時、元のルートといったフィールドが手に入る。未ログインのリクエストではMix Spaceが公開を許可した内容しか取得できないので、未公開・予約公開前・パスワード付きの内容は同期されない。
記事とノートがどう共存するか
記事とノートはAPI上の識別情報が別々になっている。
| 種類 | 不変の識別子 | 元サイトのパス |
|---|---|---|
| 記事 | post:{id} | /posts/{categorySlug}/{slug} |
| ノート | note:{id} | /notes/{nid} |
同期プログラムはタイトルを識別子に使わない。タイトルは変わりうるからだ。代わりにMix Spaceが返す不変の id を使い、記事は post:id、ノートは note:id として区別する。
通し番号とマッピング表
ここまで読んで、こう思う人もいるかもしれない。Mix Spaceの id で記事とノートを区別できるのに、なぜ数字の番号が要るのか、と。
理由は、Hexoサイトの記事パスが数字の番号を使うからだ。たとえばこうなっている。
/archives/1/
/archives/2/
/archives/3/
この数字はMix Spaceの記事IDとは直接関係がない。Hexoが内部的に使うパス番号にすぎない。
こんなふうに考えるとわかりやすい。
- Mix Spaceの
post:idやnote:idは住民票番号のようなもので、「このコンテンツが誰か」を表す。 - Hexoの
/archives/1/や/archives/2/は部屋番号のようなもので、「このコンテンツがHexoの中でどこに住んでいるか」を表す。 - ルートにある
.content-sync-map.jsonは連絡先帳のようなもので、「住民票番号」と「部屋番号」の対応を覚えておく役目を担う。
このマッピング表には、おおよそ次のことが記録される。
| 記録される内容 | 役割 |
|---|---|
post:{id} / note:{id} | Mix Spaceにおけるこのコンテンツの不変の識別子 |
1、2、3 などの数字 | Hexoにおけるこのコンテンツの固定番号 |
active / inactive | このコンテンツが今も同期対象に入っているか |
仕組みはそれほど複雑ではない。
- 初回の同期では、マッピング表にまだ記録がないので、プログラムが作成日時に沿ってコンテンツに次の数字を割り当てる。
- 2回目以降の同期では、まず
post:idやnote:idを取得し、そのあとマッピング表で以前の番号を探す。見つかればそのまま使い、見つからなければ新しい番号を割り当てる。 - Mix Spaceでタイトルやカテゴリ、slugを変更しても識別子は変わらないので、Hexoの数字も変わらない。
- Mix Spaceでコンテンツを削除したり非公開にしたりすると、対応するファイルは現在のサイトから取り除かれ、マッピング表では
inactiveと記録される。 - 一度使った数字が別のコンテンツに割り当て直されることはない。元の記事がすでに削除されていても同じだ。
こうする利点は2つある。
- タイトルやカテゴリ、Mix Spaceのslugを変更しても、Hexoのパーマリンクは変わらない。
- 一度使った番号が別のコンテンツに割り当て直されることはない。
だから source/_posts/1.md のようなファイルを手動でリネームしたり、マッピングファイルを削除したりしないこと。これらは同期プログラムが永続的なパスを保つための拠り所になっている。
元記事へのリンクはどう自動生成されるか
生成されるHexoのファイルには、いずれも original_url が書き込まれる。
original_url: https://moitr.ren/posts/categories/example
記事の原文パスはカテゴリslugと記事slugを組み合わせて作られ、ノートはその nid を使う。
文章:/posts/{category.slug}/{slug}
笔记:/notes/{nid}
ドメインは https://moitr.ren に固定されている。こうしておけば、Hexoが数字のアーカイブパスを使っていても、読者は記事ページからMix Spaceの元記事に戻れる。これはテーマ側の対応が必要になる。
本文の形式とCoreコンテナ
content_format が markdown のときは、APIが返す text を使う。lexical のときも、今の実装ではMix Spaceが用意する公開 text を優先して使っている。エディタのJSONのレンダリング規則を同期側で丸ごと再実装するのを避けるためだ。
同期の段階では、Mix Space独自のMarkdownコンテナも一部処理する。たとえばこうだ。
masonryコンテナは、Hexoテーマにすでにある画像ギャラリーの構造に変換される。successコンテナはMarkdownの引用ブロックに変換される。- 未知のコンテナはそのまま残す。自動同期が原文を誤って消してしまうのを防ぐためだ。
改行はLFに統一し、本文末尾の改行は1つだけにする。時刻はUTCのISO形式で統一する。こうしておけば、Windowsでローカル実行してもLinux上のActionsで実行しても、同じ安定したファイル内容が得られる。
GitHub Actionsのビルド順序
ワークフローの中核となる手順は次のとおりだ。
現在のスケジュール設定はこうなっている。
- cron: "20 */8 * * *"
GitHub ActionsはUTC時間を使うので、北京時間では毎日00:20、08:20、16:20にあたる。すぐに更新したいときは main にプッシュするか、Actionsのページから手動でワークフローを実行すればいい。
残しておくべきファイル
同期の仕組みは、主に次のファイルでできている。
tools/sync-content.js # APIからデータを取得し、正規化して記事を生成
.content-sync-map.json # Mix Space の識別子から数字番号への永続マッピング
.github/workflows/deploy-pages.yml
source/_posts/ # 自動生成される Hexo の記事ディレクトリ
source/_posts にある数字のファイル名が付いたMarkdownは自動生成物なので、直接編集するのはおすすめしない。次の同期で、オンラインAPIの内容がローカルでの手動編集を上書きしてしまう。
ローカルでの確認
ワークフローをコミットする前に、まずこれを実行しておくといい。
pnpm install --frozen-lockfile
pnpm content:sync
pnpm test
pnpm clean
pnpm build
APIが一時的に使えない場合、pnpm content:sync は失敗し、既存の記事を変えないはずだ。ビルドが終わったら、public/archives/ にアーカイブページと数字の記事パスが生成されているか確認しよう。
よくある注意点
1. マッピングファイルを削除しない
.content-sync-map.json を削除すると、プログラムは古い記事と数字の対応を失ってしまう。再同期のときに、その内容がまったく新しい記事として扱われ、新しいパスが割り当てられるおそれがある。
2. 1ページ目だけをリクエストしない
記事が50件を超えると、1ページ目はすべてのデータを表さなくなる。meta.pagination.total_pages に従って全ページをリクエストしないと、サイトから記事がひっそり抜け落ちてしまう。
3. APIキーをフロントエンドに置かない
今の公開コンテンツ用のAPIはログインが不要なので、Actionsからそのままリクエストすればいい。将来、認証が必要なAPIに変える場合は、認証情報をGitHub Actions Secretsに置き、ワークフローの中だけで使う。テーマのJavaScriptや生成されたHTMLに書き込んではいけない。
まとめ
コンテンツ管理はMix Spaceが、定期的な取得と検証はGitHub Actionsが、静的ページの生成はHexoが、配信はGitHub Pagesが担う。安定したリモートの識別子、使い回されない数字の番号、失敗時の保護の仕組みによって、Hexoの記事ファイルを手で管理することなく、元のブログと個人サイトの間の同期を確実に保てる。