Astro Content collectionsで技術ブログを作成

k-ito-cat/astro-markdown-blog
AstroとSveltiaCMSで作るブログサイト
概要
もともとはastro+microCMSでブログを作っていたが、マークダウンサポートが手薄く、エディタの使用感も好みではなかった。
もっとシンプルに「mdで書きたい」「gitで管理したい」と思い、AstroのContent Collectionの構成に移行。
その過程で得たサブモジュール化とDecap/Sveltia CMS連携あたりのメモをまとめる。
使用技術の変遷
使用する技術を変えるたびに、思うところが出てきてそのたびに更新してきたので、その一連の流れをざっくりと書いていく。
1. Astro + microCMS
一番最初にブログサイトを作ってみようと思い立った時、Astroをもともと使っていたということもあり、ヘッドレスCMSと統合して記事の管理はCMS、UIはAstroで作ろうという思いヘッドレスCMSを探していた。 日本発ということで何となくmicroCMSを選定して実際に繋ぎこんでみた。 APIをカスタマイズすることができて、そのAPIをもとにAstroからコンテンツを取得できるのはわかりやすさや拡張性の面でいいなと思いつつ、このCMSはWordPress同様独自のリッチテキストエディタが採用されていて、マークダウンで記述するには非公式プラグインを入れるなどをしないと書けなかったり、細かいバグ(エディタに文字を入力できない時があるなど)があったりしたので別のヘッドレスCMSへの移管を考え始めた。
変更理由
- マークダウンでの記述は実現はできるが、APIレスポンスに不満はある(HTMLパースが必要になったり)
- エディタの細かいバグが気になる
2. Astro Content Collection + hygen
とにかくマークダウンで記述したかった(書きなれた形式が一番だし、ZennやNotionで書いた記事をコピペで崩れることなく移行できるのも利便性がいい)。
考えてみればAstroはContent Collectionという、マークダウンファイルの内容をパースしてAstroファイルで使用できるようにするという機能があるのでそれを使えばいいかと思い立ち、Astroプロジェクト内でマークダウンを追加して記事を書き、その内容をブログとして表示させることにした。
Astroプロジェクト内でマークダウンファイルを管理するので、記事がgit管理できるという点もよかった。
ただ、Astroプロジェクト内で管理するということは、VSCodeなどのエディタで記事を書かなければいけないということは理解していたので、ヘッドレスCMSほど記事を書きやすさはないだろうなと思い、せめてもの記事作成時にある程度のテンプレートくらいは作成したいなと思い、hygenを採用。(さらに言うと後述するObsidianでの記述もこの時点で検討した)
CAUTION
hygen 公式サイトに飛ぶと危険なサイトへリダイレクトされる(乗っ取られている?)のでアクセスしないように注意
以下のGithubリポジトリであれば問題ないが、“hygen.io” にはアクセスしないように
変更理由
- マークダウン形式で書けるしgit管理もできてやりたいことは実現できたが、フロントマターで管理する記事のカテゴリの入力が手動なことが少し気に入らなかった
例えば以下のように、フロントマターに定義されたカテゴリをブログ表示時に取得することができるのだが、これはただのマークダウンファイルなのでサジェスト機能などもあるはずもなく、手作業で追加する必要があった。zodを使って定数管理しているカテゴリのみを受け付けるようにしているので、定義されていないカテゴリを使おうとしたら実行時エラーは出るのだが、タイポや大文字小文字の違いだけで怒られるのもストレスだった。
// astro.md
---
categories: ["Astro","SSG"]
---
## Astroについて
Astroは、ブログやマーケティング、eコマースなど、コンテンツ駆動のウェブサイトを作成するためのウェブフレームワークです。Astroは...
- せっかくならローカルだけじゃなくてマルチデバイス(異なるPC間、スマートフォンなど)で記事を書けるようにしたくなった(git pullを各デバイスで行うのはとても面倒なのでリアルタイムな同期が理想)。
3. Decap CMS(旧: NetlifyCMS)
マークダウン形式で書くことができ、保存したらつなぎこんだリポジトリに直接pushされ、mdファイルが生成される。git管理可能なマークダウン形式のヘッドレスCMS。 管理画面でカテゴリがサジェストで表示されるようになったのはかなりうれしかった。 元NetlifyCMSだったらしく、その名の通りNetlifyにホスティングしないとダメそうだったので、AmplifyにホスティングしていたものをNetlifyへ移管。
変更理由
- UI/UXがそこそこ悪い
- 記事プレビューやスマートフォン表示時のUIの質が特に悪い(レスポンシブ×)
4. Sveltia CMS
これが現在使っているもの。 DecapCMSの互換性があって、とにかく移管が楽(OAuth周りは少し調整必要かも)。 Decapの全欠点を解消。モバイル対応・UI/UXともに大幅によくなった。
ここまでで、
- 記事はすべてマークダウンで書ける
- Git管理が可能
- マルチデバイスで記事を書くことができてリアルタイムのデータの同期が可能
- ヘッドレスCMSはエディタと比べて、メタデータの管理がしやすい
といったようないい体験を求めて使用する技術を変えてきたけど、もう一つ解決したい不満があった。
記事の編集履歴をプライベートに閉じたい
差分が残るし自分が記事を更新している跡が残ることが理想だったから、記事はgit管理したかった。
ただ、ただ単に記事をgit管理すると下書きレベルの拙い文章や、publicにしたくない重要な記述(技術ブログというのもあり、ソースコードの添付などをするときに誤ってシークレットキーなどが含まれていたり)などがあると思うので、
Astroプロジェクト(ブログ本体)自体はpublicにして、マークダウンファイルが集約されるディレクトリ(src/content/{posts_name})のみをprivateリポジトリにしたく、このディレクトリをsubmoduleとして切り出すことにした。
そうすることで、マークダウンファイルの変更履歴はきちんと積み重なり、自分だけがその差分を確認できるという状況を作ることができる。
サブモジュール運用
- 親リポジトリ:astro-markdown-blog(Astro本体+UI・CMS連携・公開用)
- サブモジュール:blog-posts(Markdown記事+imagesを全てここに集約、private運用)
astro-markdown-blog/ # 親リポジトリ(Astro)
└── src/
└── content/
└── posts/ # サブモジュール(記事)
├── images/
├── foo.md
└── bar.md
構成は上記になったが、いくつかの問題はあった。
サーバー側でsubmoduleの初期化ができない
サーバー側でデプロイ前に
git submodule update --init --recursive
を実行して、.gitmodules
の内容をもとにサブモジュールの初期化を行うのだが、その際にprivateリポジトリなサブモジュールを取得できずデプロイが始まらなかった。
// .gitmodules
[submodule "src/content/posts"]
path = src/content/posts
url = git@github.com:k-ito-cat/blog-posts.git
branch = main
詳細は調べたらすぐ出てくるので省くが、GithubでDeploy keyを発行してNetlifyに公開鍵を登録するだけ。
NOTE
Deploy Key を使う場合、SSH URL を使うようにしておくこと
なので、.gitmodules
のurl
は必ずsshにしておく
SveltiaCMSで記事作成後、Netlifyのデプロイが走らない
SveltiaCMSで記事作成・更新などをした後、サブモジュールのリポジトリに直接pushされるのでサブモジュールは更新されるけど、ブログ側のリポジトリが更新されないので変更が検知されず、Netlifyのデプロイが走らなかった。
結論、Github actionsでいろいろやって無理やり解決させた。 流れは
CMSで保存時にサブモジュール(blog-posts)にcommit/pushが走る
↓
サブモジュールのGitHub Actionsでスーパープロジェクト(親リポジトリ)にイベント通知
↓
スーパープロジェクトのGitHub Actionsで、サブモジュールからイベントの通知を受け取った後、 submodule update → commit & push(スーパープロジェクトでサブモジュールの変更差分を取り入れる必要があるのでそれをしている。ポインタの更新と呼ばれる作業)
↓
ひとつ前の commit & push によってリポジトリに変更が加わりNetlifyが検知してデプロイ開始される
といったようにCMSで変更を加えたらデプロイまでの流れをGithub Actionsで自動化した。
ビルド・デプロイ
AmplifyからNetlifyに移行した兼ね合いでビルド設定をtomlファイル用にした。
Netlifyだとコンソールでビルド設定書くのがやりにくいのでnetlify.toml
を作ることが多いのかも。
[build]
command = "git submodule update --init --recursive && npm ci --cache .npm --prefer-offline && npm run build"
publish = "dist"
[build.environment]
NODE_VERSION = "22.13.0"
AstroとCMSとの連携・認証
このあたり実装途中あんまりメモ取れなかったのと、深く理解できていない部分はあるのでさらっと書く。
Decap CMS
- npmモジュールなどは不要
public/admin/index.html
を以下のドキュメントに沿って作成するだけで管理画面が作成完了する。Astroならpages/admin/index.astro
でもいいので自分はそうしている。
https://decapcms.org/docs/install-decap-cms/
- 管理画面のつなぎこみ設定やメタデータ管理などはconfig.ymlで行う
- CMSに書き込み許可するためgithubログインが必要(ログインユーザーがつなぎこんだサブモジュールのprivateリポジトリへのアクセス権があれば記事の作成や更新などが可能)。
OAuth appとOAuth認証用のハンドラが必要だった(privateリポジトリでなければNetlify Identity + Git Gatewayで簡単にできたらしい)。ハンドラについてはさすがに作りたくなかったのでnpmのパッケージを使った。SSRモードじゃないと動かなかったので注意。
OAuth appについては
https://github.com/settings/developers
「OAuth app」から新規作成する。
callbackにはhttps://{自分のサイト}/oauth/callback
を追加する必要がある。
https://www.npmjs.com/package/astro-decap-cms-oauth
Sveltia CMS移行と注意点
移行は最小限で済んだ。 ドキュメントにある通り、CDNの読み込み先を変更するだけ。 (真っ白な画面になったりしたのでDecapからの移行時はキャッシュとか非同期で読み込んでみるとかで調整したほうがいいかもしれない)
OAuthのハンドラなどはSveltiaが内包しているようで、ひとつ前のセクションでやったことは不要になった。
OAuthのハンドラは不要になったが、OAuth appは新規で作成して、NetlifyのOAuth設定(client idとシークレットの登録)は必要
// OAuth app
Application name: 任意
Homepage URL: https://{name}.netlify.app)
Authorization callback URL: https://api.netlify.com/auth/done
Sveltia CMSのエラーと回避策
Sveltia CMSで記事の新規作成時、「A path was requested for deletion which does not exist as of commit oid」エラーが出て記事を作成できなかった。
記事を全削除したり、slugや画像の日本語をすべて英字に変えたりしてみても解決せず、何が原因かわからないままこれまで使っていたblogとは別のコレクション(src/content/posts/test)を作成したらその中では記事が作成できた。
これまでposts直下にmdファイルを管理していたが、 blog用mdをblog等のサブディレクトリに移動・folderも合わせることでエラーが解消した。
おそらく/posts
のなかに.github/workflow
などがあったのでこれが影響していそうではあるが、DecapCMSでは奇跡的にうまく動いてたので問題なかったが、DecapCMSから移行するときは一応注意したほうがよさそう。
ひとまず移行後は 管理画面UIが大幅改善して、モバイル対応・プレビュー崩れも少なく満足している。 下書きやslug編集もデフォルトで対応されていて(Decapにはなかった)、使いやすい。