どうも、@uzimaru0000です。 ブログを新しくしたので、その構成について説明します。(n度目の新しいブログ)
ブログの構成
技術的な構成は以下の通りです。
- CloudFlare Pages でホスティング
- Astro でビルド
- 記事は Markdown で書かれたものが R2 にアップロード
図にするとこんな感じ
Astro
ブログ自体は、Astro で実装されています。 リポジトリはこちら
Astro 側では、アクセスされてきた記事の Markdown を R2 から取得して、marked を利用して HTML に変換しています
tsimport fm from "front-matter";
import Entry from "../../components/Entry.astro";
import Layout from "../../components/Layout.astro";
const { key } = Astro.params;
if (!key) {
return Response.json({ message: "not found" }, { status: 404 });
}
const file = await Astro.locals.runtime.env.BUCKET.get(`entries/${key}`);
if (!file) {
return Response.json({ message: "not found" }, { status: 404 });
}
const text = await file.text();
const { attributes, body } = fm<{ title: string; description: string }>(text);
また、ブログ記事は front-matter を使ってメタ情報を付与しています(タイトルとか description とか)
記事について
記事自体は、Markdown で書いているのですが管理自体は別のリポジトリ(private リポジトリ)で管理しています。 このリポジトリの main に push されると rclone を使って r2 に同期されます。 そのため、ブログ更新のフローとしては
- ブログ記事を書く
- main ブランチに入れる
- GitHubActions で rclone が r2 と同期する
という形になり、ブログ自体のビルドは不要になってます。
画像について
ブログ中の画像も記事と同一のリポジトリに入れているのですが、そのままのパスだとブログ中で参照できません。 そのため、画像用のエンドポイントを作成しました。
tsimport type { APIRoute } from "astro";
export const GET: APIRoute = async ({ params, request, locals }) => {
const obj = await locals.runtime.env.BUCKET.get(`image/${params.key}`)
if (!obj) {
return new Response(null, { status: 404 })
}
const img = await obj.arrayBuffer()
return new Response(img)
}
やってることは単純で r2 からアクセスされた key で画像を取ってきて返してあげているだけです。
まとめ
以前のブログは、Notion で管理するという形を取っていたのですが Notion のブロックを React コンポーネントにマップするのが結構ダルかったりとコード自体の管理が大変でした。 Astroでの実装はかなりシンプルなものになったので満足してます。 更新も GitHub のリポジトリに Markdown を push するだけなのでいい感じっぽいです(まだ全然記事は書いてないけど、zenn の更新がそんな感じなので) 作ったのでちゃんと更新したいですね()