Astro Container APIを使ってRSSで全文を配信する

Astro 4.9で実験的に Container API というものが追加された。これはざっくり説明すると今まで提供されていなかったAstroコンポーネントをHTML文字列に変換するAPIだ。当初はテスト用に開発されたが、いろんな場面で便利ということでユースケースが拡張されている。その中にPHPとの連携とかいう意味のわからないものもあるが、わかりやすいものとしてRSSで記事本文を配信するのに使える。ドキュメント で説明されている本文のレンダー方法は markdown-it を使って記事ソースを変換する方法で、そのままではMDXには使えない。

以下のようにやってできた。

import rss from "@astrojs/rss";
import { type APIContext } from "astro";
import { getCollection } from "astro:content";
import { experimental_AstroContainer as AstroContainer } from "astro/container";
import { loadRenderers } from "astro:container";
import { getContainerRenderer } from "@astrojs/mdx";

// YYYY-MM-DD-title形式のslugから日付を抜き出す
function pubDate(slug: string) {
  const [yyyy, mm, dd] = slug.split("-");
  return new Date([yyyy, mm, dd].join("-"));
}

export async function GET(context: APIContext) {
  if (!context.site) {
    throw new TypeError("context.site falsy");
  }

  const container = await AstroContainer.create({
    renderers: await loadRenderers([getContainerRenderer()]),
  });

  const blog = await getCollection("blog");

  // yyyymmddをslugに入れてるせいかソートされてるのでreverseするだけでいいっぽい?
  blog.reverse();

  return rss({
    title: "fuku.day/blog",
    description: "AumyF's blog",
    site: context.site,
    items: await Promise.all(
      blog.map(async (post) => ({
        title: post.data.title,
        description: post.data.description,
        link: `/blog/${post.slug}`,
        pubDate: pubDate(post.slug),
        content: await container.renderToString((await post.render()).Content),
      }))
    ),
  });
}

なお、astro.config.jssite を設定しておく必要がある。

export default defineConfig({
  site: "https://fuku.day",
  // あれやこれや
});

astro/container はContainer API本体で、astro:container は4.101で追加されたContainer APIのヘルパーユーティリティが入ったバーチャルモジュールだ(このへんの使い分けがどのようになされているのかは知らない)。レンダーしたいコンテンツ中でReactやVueなどを使っているなら @astrojs/react などそれぞれの getContainerRenderer も使うことになる。

おわりに

Container APIでRSS全文配信をするという考えは_yuheiyさんのツイートのものです。

Footnotes

  1. モジュール設定のミスがあって正確には4.10.3まで上げないと動かない