プログラミング
プログラミング
  • 2024/06/22
  • 2024/09/17

AstroブログにOG画像の自動生成をつける

satoriと@resvg/resvg-jsを使用して、記事のタイトルを含むOG画像を自動生成する機能をつけました。

戦略


フロー

Vercelが提供するライブラリである satori はHTMLをSVGへ変換することができます。そうして得られたSVGを @resvg/resvg-js でPNG画像に変換することで、OG画像とします。

$ yarn add satori @resvg/resvg-js

Reactのインストール

ベースとなるHTMLはReact JSXで作成するため、Reactインテグレーションを導入します。

$ yarn astro add react

ファイルを用意する


エンドポイント

Astroでは、/[slug].png.ts というファイルを用意することで、静的生成の度にルートパラメータ slug に対応するOG画像 [slug].png を吐き出すエンドポイントをつくることができます。

import { getArticles, getArticleBySlug } from "src/lib/client"; 
import { getOgImageResponse } from "src/components/OgImage";
import type { APIContext } from "astro";

export const getStaticPaths = async () => {
  const articles = await getArticles();

  if (!articles || articles.length <= 0) return;

  return articles.map((article) => ({
	params: { slug: article.slug },
  }));
};

export const GET = async ({ params }: APIContext) => {
  const article = await getArticleBySlug(params.slug);

  if (!article) return;

  const ogImageResponse = await getOgImageResponse(article.title);

  return ogImageResponse;
};

ここでは、getOgImageResponse という関数を OgImage コンポーネントに切り出しています。

以降の処理を処理をセクションごとにまとめます。

画像を生成するコンポーネント

import fs from "fs";
import satori from "satori";
import { Resvg } from "@resvg/resvg-js";

export const getOgImageResponse = async (text: string): Promise<Response> => {
  const fontBold = fs.readFileSync("src/fonts/NotoSansJP-Bold.ttf");

  const width = 1200;
  const height = 630;

  const svg = await satori(
    <main style={{
      height: "100%",
      width: "100%",
      background: "#fff",
      position: "relative",
    }}>
    {text}
    </main>,
      {
	width,
	height,
	fonts: [
          {
            name: "Noto Sans JP",
	    data: fontRegular,
	    weight: 700,
	    style: "normal",
          },
	],
      }
    );

    const resvg = new Resvg(svg, {
      font: {
        loadSystemFonts: false,
      },
      fitTo: {
        mode: "width",
        value: width,
      },
   });

  const image = resvg.render();
  return new Response(image.asPng(), {
    headers: {
      "Content-Type": "image/png",
    },
  });
};

satori はDOMからSVGを生成します。これを Resvg に渡すことでPNG画像のインスタンスを生成し、これをHTTPレスポンスの形で返却します。

画像のURLを渡す

metaタグの設定があるコンポーネント(記事ページのレイアウト)に画像のURLを渡します。

課題


画像生成がボトルネックになってかなり時間がかかることがありました。

ローカルサーバ起動時のエラー

@resvg/resvg-js をインストールしてから、$ astro dev がエラーを吐いてローカルサーバが立ち上がらなくなる現象が発生したため、このIssue にある内容をもとに astro.config.mjs に以下を追記しました。

vite: {
    ssr: {
        external: ['@resvg/resvg-js']
    },
    optimizeDeps: {
        exclude: ["@resvg/resvg-js"]
    },
}

関連記事


    記事がありません

Shota Inoue
Shota Inoue

大学生 | 化学・Webプログラミング・統計学など