このVault(Obsidian)で書いたノートを、そのまま個人ブログとして公開する仕組みを作った。記事の一次情報源はあくまでVaultで、ブログ側(Astro)は「表示するだけ」の立場にしたかったので、その構成をメモしておく。
notes (このVault, private)
└── 60_blog/ ← ここに置いた status:active なノートだけが公開対象
↓ push (60_blog/** に変更があったとき)
GitHub Actions: repository_dispatch を astro-blog へ送信
↓
astro-blog (Astro, public)
GitHub Actions: notesをcheckoutしてactive記事だけをミラー同期 → commit & push
↓
Cloudflare Pages (Git連携) が自動ビルド・デプロイ
Vaultとブログを別リポジトリに分けているのは、Vaultには60_blog/以外にも仕事のメモや日記など公開したくないノートが大量にあるため。公開対象はstatus: activeのノートだけ、というフィルタをVault側のfrontmatterで管理し、CIがそれをそのまま反映する形にした。
AstroのContent Layer(glob()ローダー)で、Vaultのfrontmatterスキーマ(title / aliases / created / updated / tags / status)をそのままzodスキーマにした。Astro向けにpubDateなどへ変換する層を挟まなかったので、同期スクリプトはfrontmatterを一切書き換えず、該当ファイルをコピーするだけで済んでいる。
const blog = defineCollection({
loader: glob({ pattern: '**/*.md', base: './src/content/blog' }),
schema: z.object({
title: z.string(),
aliases: z.array(z.string()).default([]),
created: z.coerce.date(),
updated: z.coerce.date(),
tags: z.array(z.string()).default([]),
status: z.literal('active'),
}),
});
Obsidianの[[wikilink]]をAstro側で解決するために既存の@portaljs/remark-wiki-linkを入れたが、Astro 7が使う最新のmicromark/mdast-util-from-markdown(v4/v2系)と噛み合わず、ビルドがクラッシュした。このパッケージが前提にしているmicromarkのメジャーバージョンが古く、依存解決で新しい方に寄せられてトークナイザーの内部状態が壊れるのが原因だった。
回避策として、mdastのtext nodeを正規表現で走査して[[target]] / [[target|alias]]を独自にlinkノードへ置き換えるだけの小さなremarkプラグインを書いた。既存のmicromark拡張機構に頼らないぶん、バージョン間の非互換に巻き込まれにくい。存在しないノートへのリンクはリンクにせず、静かにプレーンテキストへ落とすようにしている。
VaultリポジトリのCIからブログリポジトリを更新する、という構成にする上で地味にハマったのが、GitHub Actionsが自動発行するGITHUB_TOKENは実行中のリポジトリ自身にしかアクセスできないという制約だった。同じアカウントが持つリポジトリ同士でも例外はない。
そのため今回は最小権限のfine-grained PATを2つ用意した。
repository_dispatchを送るための書き込み権限PAT同一リポジトリ内で完結する処理(同期後に自分自身へcommitする部分)はGITHUB_TOKENのままで問題ない。クロスリポジトリの橋渡しだけ、明示的なPATが必要になる。
デザイン部分はTailwind v4 + daisyUI v5、コンポーネントはReactで書いている。ただしclient:*ディレクティブを一切付けていないので、Astroのアイランドアーキテクチャによりビルド時に静的HTMLへ変換されるだけで、ブラウザにJSは一切送られない。Reactの書き味を保ちながら、静的ブログとしてのパフォーマンス特性は崩さずに済んでいる。
Vault側で記事を書いてstatus: activeにし、60_blog/に置いてpushするだけで、あとは自動でブログに反映されるところまで組めた。デザインの調整はブログリポジトリ側だけで完結するので、コンテンツとデザインの更新サイクルを分離できているのも狙い通り。