Astroブログでsatoriを使って記事ごとのOGP画像を自動生成する
はじめに
少し前にGatsbyからAstroにブログを移行したこのブログだが、satoriを使って記事ごとにGOP画像を自動生成できるようにした。
今回はその方法を残しておく。
画像生成に必要な準備
今回使用するsatoriはVercelが公開しているライブラリで、JSX構文で書かれた要素をSVGに変換してくれる。JSXを処理するのでreact
パッケージが必要となる。一方、DOMの差分処理などはする必要がないのでreact-dom
は不要だ。
npm install satori react @types/react
また、satoriが生成するのはSVG画像であるため、そのままではOGP画像には使用できない。今回はsatoriが作成したSVG画像をPNGに変換するためresvg
を使用する。
npm install @resvg/resvg-js
最後に、satoriで生成する画像の中で文字を扱うにはフォントデータが必要となる。今回はGoogleFontsからNoto Sans Japanese
をダウンロードして使用する。
satoriで画像生成
まずは画像を作成するための関数をsrc/utils/ogp.tsx
作成する。
import type { CollectionEntry } from 'astro:content';
import React from 'react'
import satori from 'satori';
import { Resvg } from '@resvg/resvg-js';
import RegularFont from "../assets/fonts/NotoSansJP-Regular.ttf";
import BoldFont from "../assets/fonts/NotoSansJP-Bold.ttf";
const generateOgpImage = async (element: React.ReactNode) => {
const svg = await satori(
element,
{
width: 1200,
height: 630,
fonts: [
{
name: "Noto Sans JP",
data: Buffer.from(RegularFont),
style: "normal",
weight: 400,
},
{
name: "Noto Sans JP",
data: Buffer.from(BoldFont),
style: "normal",
weight: 600,
},
],
}
);
const resvg = new Resvg(svg, {
fitTo: {
mode: "width",
value: 1200,
},
});
const image = resvg.render();
return image.asPng();
}
export const generateBlogPostOgpImage = (post: CollectionEntry<"post">) => {
const { title, createdAt, tags } = post.data;
return generateOgpImage((
<div>blog title is {title}</div>
));
}
export const generateSiteOgpImage = () => {
return generateOgpImage((
<div>site ogp</div>
));
};
generateBlogPostOgpImage
はブログ記事ページ用に、generateSiteOgpImage
はそれ以外のページに使用する。それ以外のページはsatoriではなく生のOGP画像をpublic
ディレクトリにでも配置して良いが、好きなときにスタイルを変更したりしたいのでこのようにしている。
satori
のオプションについては公式のドキュメントを参照してほしい。また、生成された画像がどのようになっているかは公式のPlayGroundで確認するのが便利だ。
また、フォントファイルの読み込みにはfs.readFileSync
を使いたかったが、Astroではno such file or directory
とエラーが出てできなかった。Astroでフォントデータを読み込む方法については以下に詳しく書いたので参照してみてほしい。
OGP画像用のエンドポイントを作成する
これでOGP画像は作成できるようになったので、それを参照するためのエンドポイントを作成する。今回は以下のようなエンドポイントでOGP画像を参照できるようにする。
- 記事ページ:
/posts/ogp/[slug].png
- その他のページ:
/ogp.png
それぞれ、src/pages/posts/ogp/[slug].png.ts
とsrc/pages/ogp.png.ts
というファイルを作成する。Astroにおける静的なファイルを返すエンドポイントを作成するには、filename.[返したいファイルの拡張子].[js|ts]
というファイルを作成する。詳しくは以下を参照してほしい。
// src/pages/posts/ogp/[slug].png.ts
import type { APIRoute } from "astro";
import { getCollection } from "astro:content";
import { getPostSlug } from "../../../utils/posts";
import { generateBlogPostOgpImage } from "../../../utils/ogp";
export const GET: APIRoute = async ({ params }) => {
const slug = params.slug;
const posts = await getCollection("post");
const post = posts.find((p) => p.slug === slug);
const png = await generateBlogPostOgpImage(post!);
return new Response(png, {
headers: {
'Content-Type': 'image/png',
},
});
};
export async function getStaticPaths() {
const posts = await getCollection("post");
return posts.map((post) => {
return {
params: { slug: post.slug },
}
})
}
// src/pages/ogp.png.ts
import type { APIRoute } from "astro";
import { generateSiteOgpImage } from "../utils/ogp";
export const GET: APIRoute = async () => {
const png = await generateSiteOgpImage();
return new Response(png, {
headers: {
'Content-Type': 'image/png',
},
});
};
これで各エンドポイントからOGP画像が返ってくるようになった。
メタタグにOGP画像を設定する
最後に、各ページのメタタグにOGP画像を設定する。自分は全てのページでBaseLayout
というレイアウトを使用しているので、その中でメタタグに値を渡して設定した。
---
import { siteConfig } from '../config'
interface Props {
title: string;
description: string;
ogUrl?: string;
}
const {
title,
description,
ogUrl = `${siteConfig.url}/ogp.png`,
} = Astro.props
---
<!doctype html>
<html lang="en">
<head>
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={ogUrl} />
<meta property="og:type" content="website" />
</head>
記事ページで使用する際は以下のようにする。それ以外のページではogUrl
を渡さなければ、デフォルトでOGP画像が設定される。
---
const { title, description } = post.data;
---
<BaseLayout
title={`${title} | ${siteConfig.title}`}
description={description}
ogUrl={`${siteConfig.url}/posts/ogp/${post.slug}.png`}
>
<Content />
</BaseLayout>
これで記事ごとのページと、それ以外のページにもOGP画像を設定することができた。
おわり
satoriはよく使っているJSX構文でSVGが作成できる便利なライブラリだ。ぜひ試してみてほしい。