endaaman.com

2024-11-12

更新履歴

Obsidianを使ったオレオレCMSのススメ

SSRのCloudflareキャッシュについても解説

オレオレCMSの概要

本ブログは、Obsidianで書いたものが自動的に配信されるようなシステムになっている。サーバーは自宅のProxmoxマシン上のUbuntu VMで、記事の一覧を読み取ってREST APIで配信するバックエンドと、SSRアプリのフロントエンドに別れ、それぞれDockerコンテナで動いている。そして、記事は基本的に自分が更新しなければ内容は変わらないので、すべてのページをCloudflareにキャッシュしている。

Obsidianの快適な編集環境を有効活用しながら、なおかつレスポンスも良いブログとしても配信できるシステムに非常に満足している。ここに紹介もかねて記事としてまとめる。

オレオレCMSのoutline

Obsidianを使った記事管理

このブログは、Obsidianを使ったautomaticなCMSになっている。

obsidian-edit.png このようにObsidianで記述した内容がそのままブログに反映される

実際に上記のように記事を書いている。Vault直下にBlog というディレクトリを作り、これをCMSのルートにしている。必ず一つのサブフォルダを挟み、update/2024-11-12_obsidian-oreore-cms.md というようにファイルを作ると https://endaaman.com/update/obsidian-oreore-cms として公開されるようになっている。

Syncthingを使った同期

そもそもObsidianは無料だとエディタしか使えないので、同期するためには自分でインフラを構築する必要がある。そのために Syncthing を採用した。

Syncthingの管理画面。デフォルトはlocalhost:8384

P2Pで動作するプライベートDropboxみたいなものだ。Syncthingの最大の長所は、スマートフォンと同期できることだろう。LANはNATトラバースでデバイスを検知し、スマホでもPCでも即座に書いたものが同期される。NASなど一つ中央集権的なデバイスを用意すれば、その他の使い勝手はDropboxとほとんど変わらない。VPNも使えばデバイス間のやりとりはより柔軟なものになるだろう。

Vaultをサーバーと同期する

Syncthingで同期するデバイスの一つに自宅のQNAP NASがあり、同一LANに存在するサーバーVM上にnfsでVaultのディレクトリをマウントしている。具体的にはNAS上に /share/Obsidian を、VM側では /mnt/Obsidian にマウントする。この /mnt/Obsidian/Blog を Dockerコンテナに volume で引き渡すことでサーバーアプリケーションからアクセスできるようにしている。開発時はローカルで同期したディレクトリのシンボリックリンクを張るだけである。

静的ファイルのあつかい

/mnt/Obsidian/Blog と一緒に/mnt/Obsidian/static もVMから見えるようになるので、これも volume にしてDockerに渡してNginxのaliasで使って配信する。 /static/images/obsidian-oreore-cms/obsidian-edit.png のように参照すると、ObsidianでもHTMLでもちゃんと画像が表示される(Obsidianは曖昧に画像が参照できてとてもえらい)。

記事をREST APIで配信

サーバーアプリケーションはPythonとFastAPIで作っている。採用理由はコード記述量が少なく、最近機械学習でPythonばかり書いているためだ。Blog以下のMarkdownファイルをまるっと読み込むわけだが、このパース部分の仕様が一番重要である。仕様さえ守られていればアプリケーションは何でもよい。実際にアプリケーション部分はこれまではnode.js+KoaGo+BeeGoPython+FastAPIと変遷しているが、記事を捨てること無くやりくりしてきている。

パースのルールは

  • サブディレクトリをカテゴリとして扱う
  • ファイル名はYYYY-MM-DD_slug.mdとする
    • 日付とslugを必須とする
    • この書式に従わないファイルは無視する
  • 個別ページのパスは /{category}/{slug} とする
  • 本文は body とし、その他のプロパティをFrontmatterで管理
    • 必須項目はなし
      • 内容のバリデーションができないから
    • title, draft, tags, digestなどを設定できる

日付を頭に付けるのは、タイムスタンプだと引っ越しのときに情報が脱落する可能性があること、ソートできる一覧性のためである。

JavaScriptのほうがMarkdown関連は充実しているのでHTMLへのパースはフロントエンドで行う。実際に https://endaaman.com/api/articles/ を開くと一覧が得られる。

変更の監視とキャッシュパージ

watchdogでルートディレクトリを監視し、記事が変更されるたびにメモリ上にキャッシュする。変更があれば必ずこのパース処理が走るので、そのタイミングでCloudflareのAPIからキャッシュパージを行う。念のためGETで手動でキャッシュパージする秘密のエントリポイントも作っている。

SvelteKitでWebサービス化

本ブログは、トップページの一覧と、各記事詳細の、2種類のページしか無い。トップページでは一覧のみを取得する。一覧のときはペイロードが大きくなるので bodyを含めない。個別ページごとに対応するREST APIを触るようにしている。

HTMLへの変換には markdown-itを使っている。ここでObsidianの独自機能などを対応させる。拡張の一例を上げると GitHub Alerts は特に便利で多用している。

GitHub Alertsとは

このようなハイライトされた特殊なブロックである。GitHub独自のシンタックスだが、Obsidianも対応しており、markdown-itにもそのようなプラグインがある

あとは QiitaやZennのようにコードブロックにファイル名を表示するのもちょっと細工が必要だったりする。

> [!NOTE] GitHub Alertsとは
> このように特殊なブロックを埋め込める。GitHub独自のシンタックスだが、
> Obsidianも対応しており、markdown-itにもそのようなプラグインがある

nginxで配信・Cloudflareのキャッシュ設定

  • / - フロントエンド
  • /api - バックエンド
  • /static - 静的ファイル(Nginx)

となっているが、これは全部Cloudflareでキャッシュする。

server {
  listen 80;
  server_name endaaman.com;
  add_header Cache-Control "public, max-age=2592000, s-maxage=2592000";
  location / {
    proxy_pass http://localhost:8000;
  }

  location /api {
    proxy_pass http://blog-backend:3000;
  }

  location /static {
    alias /app/static;
    gzip on;
    gzip_types image/jpeg image/png image/webp image/gif image/svg+xml;
  }
}

このように全部一ヶ月(2592000秒)にしている。毎回自動的にキャシュパージが入るので、かなり長くしても全く問題ないのである。

ここで重要なのは、変にnginxでキャッシュを持たないことだ。キャッシュのライフタイムがわからず、完全に意味不明なことになる。

SvelteKitのprefetchとブラウザ キャッシュ TTL

Cloudflareではブラウザ キャッシュも有効にしている。SveteKitを使っていると、リンクにホバーするだけで +page.jsloadが発火し、ページ遷移前にfetchなどが飛ぶようになっている。事前にコンテンツを取得することで高速化を図るものだが、これも設定しておけばリクエストはCloudflareエッジにすら行かず、ブラウザのディスクキャッシュから即座に解決されるようになる。

まとめ

個人的にはかなり快適なコンテンツ作成環境が整ったともう。 このようなブログシステムにせずとも、Obsidian + Syncthing (+NASもあるとなお良い)は革命的に便利なので、この組み合わせだけでもぜひ試してみてほしい。


©2024 endaaman.com