orbit.supply
orbit.supply

AstroとObsidianでブログを作成しました

AstroObsidianBlog
公開/更新
保存
2024.11.02
↪︎ 2024.11.09
2024.12.10

AstroとObsidianでブログを作成しました

AstroObsidianBlog

目指すかたち

構成

項目サービス
執筆環境Obsidian
サイトジェネレーターAstro
コンテンツ管理Github
ホスティングCloudflare Pages
ドメインCloudflare DNS, Porkbun

執筆フロー

✍️ Obsidian → 👍 Github push → ⚙️ Cloudflare Pages build & deploy → ✅ Publish

ローカル確認環境

✍️ VSCode → 🚀 Astro $ npm run dev → ✅ localhost:4321

選定背景

Webフロントエンドフレームワークの選択

アーキテクチャの選択: なぜSSGなのか

WordPress のようなSSR、Astro のようなSSG、React ベースのSPAの3つの選択肢を検討しました。

まずSSRについて、WordPress や Django は動的なコンテンツ生成が容易な反面、サーバーの継続的なメンテナンスが必要です。「メンテナンスの容易性」という観点から、この選択肢は見送りました。

React とそのエコシステムなどのSPAは、Webアプリケーション向けの機能が豊富です。ゆえに単純なブログコンテンツの配信には不要な機能も多く、オーバースペックだと判断しました。

最終的にSSGを採用することにしました。サーバーレスで運用でき、CDN配信との相性も良好です。ビルドは Cloudflare Pages 側で実行されるので、たとえビルド時間が長くなったとしても執筆プロセスへの影響はありません。

SSGの中でAstroを選んだ理由

SSGの選定では、Markdown のネイティブサポートを重視しました。非公式のプラグインによる対応では、メンテナンス性に不安が残るためです。

Hugo や Gatsby も検討しましたが、テーマへの依存度が気になりました。カスタマイズの自由度を考えると、必要な機能を積み上げていける方が良さそうです。

また、普段から Svelte を使用しているので、Astro の開発体験は馴染み深いものでした。Island Architecture によるコンポーネントの組み込みや、node package の扱いを含めた開発フローが自然な形で構築できそうだと考えています。

フレーム
ワーク
SPA/
MPA
SSG/
SSR
初回
ロード
ページ
遷移
.mdlangサーバ
メンテ
Hugo, GatsbyMPASSG+++BuiltinGo, JS不要沢山
AstroMPASSG+++BuiltinJS/TS不要新興
SvelteSPA*SSG*++++PluginJS/TS構成新興
React, VueSPA*SSG*++++PluginJS/TS構成沢山
AngularSPA*SSG*++++PluginTS構成
Django, FlaskMPASSR+++++Python必要レア
WordPressMPASSR++++++++PluginPHP必要沢山
DrupalMPA*SSR*+++++PluginPHP必要デジ庁

記号凡例:
*: 例えば、SPAが基本だが、拡張機能でMPAもできるなど、基本構成以外の実装も可能なことを示す
+: ロード時間の感覚的な印象。+が多いほど時間がかかる(あくまで個人的な体感)

執筆環境の選択: なぜObsidianなのか

コンテンツ管理の選択肢としては、Notion のような All in One のサービスや、最近人気のある headless CMS ( Newt や microCMS ) を検討しました。

Notion は Markdown エクスポートも可能で書き心地も良いのですが、オフライン執筆ができないことが気になります。またAPIを介したコンテンツ取得が必要で、ビルドの依存関係や複雑さが増えてしまうことも、「メンテナンスの容易性」という観点には合致しませんでした。

microCMS や Newt などの headless CMS はここ数年定着してきており魅力的です。ただし個人ブログでの利用ではフリープランの制限にすぐ引っかかりそうで、運用コストの目安を外れてしまいます。また Markdown のエクスポートは可能とはいえ、画像の管理まで含めると移行の手間は残りそうです。

結果的にObsidianを選択したのは、以下の理由からです:

ホスティングの選択: なぜCloudflare Pagesなのか

静的サイトのホスティング先として、Vercel、Netlify、GitHub Pages、Cloudflare Pages を比較検討しました。

Vercelは無料プランでの商用利用に制限があります。個人ブログとはいえ、将来的な制約は避けたいところです。
Netlifyは充実した機能と実績がある一方で、ホスティングサーバーが遠いという話をよく耳にします 1。CDNは効くものの、その前段の応答が気になりました。
GitHub Pages はシンプルで安定していますが、デプロイフローを自前で構築する必要があります。CIの設定など、本質的でない部分に時間を取られそうでした。

最終的に Cloudflare Pages を選んだ決め手は次の通りです:

Obsidianの設定

オプション > ファイルとリンク から

Astroのフォルダ構成

project-root //必須configファイル等はこのtreeから省略
├── public: 未使用
├── src
│   ├── components //動かなくなったら切り捨てればよい機能
│   │   ├── Link-copy-button.svelte //with svelte-copy
│   │   └── QR-card*.{svelte, ts} //with html-to-image
│   ├── content
│   │   ├── config.ts //collectionのtype設定
│   │   └── note //obsidianのvault
│   │       ├── .obridian //Squid(custom)はpluginフォルダからシムリンク
│   │       ├── _images //記事の画像はここに配置
│   │       ├── _template
│   │       │   └── note.md //with Templater & Squid(custom)
│   │       └── notes*.md //ここでnoteを書く
│   ├── lib //動かないと困る機能
│   │   └── note.ts //noteに関わる関数
│   ├── layouts
│   │   └── Layout.astro //ページのlayout
│   ├── pages
│   │   ├── index.astro //トップページ
│   │   └── note
│   │       └── [id].astro //noteページ
│   └── styles
│       └── global.css
├── tailwind.config.mjs //最低限→残りはglobal.css
├── astro.config.mjs //プラグインの登録など
└── tsconfig.json //noteフォルダの除外

依存ライブラリ

Astro関連

Markdown拡張

QRカード機能

Obsidianプラグイン

覚えておくこと

URLの設計思想

記事のURLをどう設計するかは、長期に渡って影響する重要な決定です。

要素URLに含める場合URLに含めない場合
タイトル✅ 内容が一目瞭然
✅ SEOに有利?
🤔 タイトル変更で乖離
🤔 日本語だとURLが長くなる
✅ ユニークで永続的なリンク
✅ 短いURL
🤔 内容の推測が難しい
日付✅ 時系列が分かりやすい
✅ アーカイブ構造が自然
🤔 作成日or更新日の選択が必要
🤔 更新しても古い記事と誤解
✅ コンテンツを日付で判断されない
✅ シンプルなURL
🤔 時系列の把握が難しい

検討の結果、リンクの永続性・URLの短さ・コンテンツの寿命を日付に依存させないことの3つを重視して、URLにはタイトルも日付も含めない設計を選択しました。
時系列の表示管理はフロントエンドで制御し、更新履歴はGitで追跡できます。

IDの生成ルール(Squid)

記事のID(記事のパス)の生成には Squid を採用しました。
具体的な設定は以下のとおりです:

  1. Unix timestamp の先頭10桁を使用(ミリ秒は省略)
  2. Squid でエンコード
  3. Z-Base-32 の文字セットに制限(口頭で伝えやすくするために大文字小文字の混在を避ける目的で)

これにより py647x8n のような8文字程度の短いIDが生成されます。

公開制御の仕組みとルール

記事の公開制御は3段階で実装:

  1. _draftフォルダ内のファイルは.gitignoreで除外
  2. ファイル名先頭の✏️マークのファイルも.gitignoreで除外
  3. フロントマターのpublic: falseでビルドから除外

つまり、public: trueかつファイル名に✏️がなく、_draftフォルダ外にあるファイルのみが公開されます。

QRカード機能の目的

スマートフォンの写真アプリは、実は優れたブックマーク管理ツールになる可能性があります。例えば、読んだ本の気になるページをスキャンして写真に収めておくことはよくある使い方です。この発想を Web ページの管理にも応用できないかと考えました。

QRカード機能は、Webページへのリンクを写真として保存することを実現します。QRコードを含む画像を生成することで、写真アプリ上で紙の書籍スキャンと一緒にWebページも管理できます。さらに、写真アプリの画像内テキスト検索を使えば、保存したページの内容も検索可能です。スマートフォンからはQRコードを直接タップしてページを開けるため、実用的なブックマークとして機能するはずです。

実装の詳細

  1. node-qrcode でQRコードを生成
  2. html-to-image でカード全体を画像化
  3. ※ Firefox環境での問題あり(今後の課題)

今後に持ち越しの課題

Footnotes

  1. Cloudflare Pages・Vercel ・Netlify の違いや使い分けをまとめる