Written by

tsuka-ryu

At

Sun Jan 11 2026

TakumiでOG Imageを生成した

Takumiを使用してブログにOG画像生成機能を追加しました

あけましておめでとうございます。年末年始寝込んでいたら、もう1月前半が終わってしまいそうです。まだ病み上がりで元気な感じがないので、早く元気になりたいです。今回はブログにOG画像生成機能を追加したので紹介します。

Takumi

Fumadocsのドキュメントに出てきたので、今回はTakumiというライブラリを使ってOG画像を生成してみました。

OG画像生成

OG画像生成といえば、vercelのsatoriと、それをベースにした"next/og"の機能だと思います。自分も過去にsatoriを使って動的OG画像生成をしたことがあります。

ただ、satoriのCSSのサポート状況を眺めながら実装するのは地味に骨が折れた記憶があります。いまはどうなんでしょう。

Takumiの特徴

特徴は公式ドキュメントリポジトリに記載されてるので詳細は割愛しますが、自分が良さそうだなと思ったのは下記です。

  • Rustで実装されているため高速(ファイル数が増えるとビルド時間が長くなりがちな場合に有効)
  • Tailwind CSS ファースト

実装

Fumadocsのドキュメント通りに実装するだけです。見た目はTakumiのtemplateからコピペしてちょっと整えました

fontの読み込みについてもドキュメントに書かれていて、今回はNoto Sans JPを指定してます。ただフォントファイルが9.3MBあるため、サブセット化をしたいと思っています。

app/og/blog/[...slug]/route.tsx
// import文は割愛

export async function GET(_req: Request, { params }: RouteContext<"/og/blog/[...slug]">) {
  const { slug } = await params;
  const page = blog.getPage(slug.slice(0, -1));
  if (!page) notFound();

  // Load Noto Sans JP font
  const fontPath = path.join(process.cwd(), "public/fonts/NotoSansJP.ttf");
  const fontData = await fs.readFile(fontPath);

  // Load background image directly from filesystem
  const backgroundImagePath = path.join(process.cwd(), "public/og-background-image.webp");
  const backgroundImageData = await fs.readFile(backgroundImagePath);
  const backgroundImageBase64 = `data:image/webp;base64,${backgroundImageData.toString("base64")}`;

  // Format date for display
  const formattedDate = page.data.date
    ? new Date(page.data.date).toLocaleDateString("ja-JP", {
        year: "numeric",
        month: "long",
        day: "numeric",
      })
    : "";

  return new ImageResponse(
    <BlogPost
      title={page.data.title}
      author={page.data.author}
      date={formattedDate}
      // category={""} TODO: カテゴリ実装したら追加
      avatar={"https://avatars.githubusercontent.com/u/69495387"}
      backgroundImage={`url(${backgroundImageBase64})`}
    />,
    {
      width: 1200,
      height: 630,
      format: "webp",
      fonts: [
        {
          name: "Noto Sans JP",
          data: fontData,
          weight: 400,
          style: "normal",
        },
      ],
    },
  );
}

完成

実装で特に苦労することもなく、こんな感じになりました。満足です。

OG画像のサンプル

2026/01/17追記

backgroundImageを使っていて、かつ記事数が4つを超えるとビルドが終わらなくなる現象が発生した。 原因の追究は後回しにして、いったんbackgroundImageを使うのをやめた。 Persistent Imagesを適用しようとしたが失敗した。Next.jsの静的ビルド時にfs.readFileでpublicディレクトリを読み込む際のパス解決に問題があるのかもしれない。 最小構成で再現するか調べてみたい。


最終更新: 2026年1月17日