新しいブログの構成

どうも、@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 に同期されます。 そのため、ブログ更新のフローとしては

  1. ブログ記事を書く
  2. main ブランチに入れる
  3. 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 の更新がそんな感じなので) 作ったのでちゃんと更新したいですね()