🐡

[2023年6月版]Astro.js 小ネタ集 その10 OGP画像とsatori

2023/06/29に公開

前回に引き続きAstro.jsでの小ネタを紹介していきます。

ここではAstroの画像生成エンドポイントと、satoriでのSVG画像生成についてご紹介いたします。

Satori で OGP画像を動的に生成したい

今回はTwitterやZennなどで埋め込まれるOGPメタタグ用に画像エンドポイントを作成します。

さらに、そこでブログのタイトルなど文字列を含む画像を生成するため、satoriを使用してHTMLでレイアウトを定義したSVG画像の生成にチャレンジしてみます。

以下のパッケージを使用します。

準備

まずはパッケージのインストールから。

$ npm i satori satori-html @resvg/resvg-js

また画像内で日本語フォントをきれいにレンダリングするためにお気に入りのフォントファイルをダウンロードしてきます。

今回は さわらびゴシック を使用しています。
フォントファイルをダウンロードしたら、src/lib/sawarabi-gothic-medium.ttf などのようなフォルダに入れておきます。

画像のエンドポイント

続いてOGP画像用エンドポイントを作成します。ざっくりと載せてしまいます。

src/pages/images/[slug].png.ts
import satori from 'satori';
import { html } from 'satori-html';
import Sawarabi from '../../lib/sawarabi-gothic-medium.ttf'

export async function getStaticPaths() {
  return (await getCollection('blog')).map((post) => ({
    params: { slug: post.slug },
    props: { collection: 'blog' }
  }) as any);
}

const height = 630;
const width = 1200;

export async function get({ url, params, props }: APIContext) {
  const { slug } = params;
  const { collection } = props as { collection: 'blog' };

  let post: CollectionEntry<'blog'> | undefined;
  let qrCode : Buffer | undefined;

  post = await getEntryBySlug(collection, slug);

  const out = html`<div tw="flex w-full flex-col bg-red-400">
    <div tw="h-93 mx-6 px-4 flex flex-col bg-white">
      <h1 tw="text-4xl">${post?.data.title}</h1>
      <p tw="text-[1.8rem] w-[56rem] bottom-0">${(post?.data as any).description ?? ''}</p>
    </div>
    <div tw="flex flex-row mx-6 bg-white rounded-b-2xl mb-8">
      <p tw="text-[1.5rem] text-zinc-600">あそぴテックのごった煮ブログ</p>
    </div>
  </div>`

  let svg = await satori(out, {
    fonts: [
      {
        name: 'Open Sans',
        data: Buffer.from(Sawarabi),
        style: 'normal'
      }
    ],
    height,
    width
  });

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

  const image = resvg.render();

  return {
    headers: {
      'Content-Type': 'image/png',
      'Cache-Control': 'public, max-age=31536000, immutable'
    },

    body: image.asPng()
  }
}

まず、getStaticPaths()blog コレクションからすべての slug に対する [slug].png のファイル名を生成してます。

続いて、export async function get({ url, params, props }: APIContext)GET に対するエンドポイントを定義しています。

post = await getEntryBySlug(collection, slug); はその通り、slugからブログのエントリを読み込んでいます。

"html`"で始まる部分でレイアウトを定義するHTMLを記載しています。文字列で指定したHTMLは satori-html のおかげで仮想DOMに変換されております。

そういえば、tw= で tailwindcss のクラス名が効いてます。これはありがたいです。

let svg = await satori(out, {... でフォントと画像サイズを指定してSVGの生成を行います。

const resvg = new Resvg(svg, {... .... resvg.render(); でSVGをPNG画像にレンダリングします。

最後の return {headers: {'Content-Type': 'image/png','Cache-Control': 'public, max-age=31536000, immutable'},body: image.asPng()} でPNG画像形式でデータを返却します。

HTMLのヘッダーにOGPのメタタグを追加

仕上げに HTMLのヘッダーでMetaタグを設定します。

src/layouts/Base.astro
---
...
export interface Props {
	title?: string;
  description?: string;
  thumbnail?: {
    src: string;
  };
}

const { title, description, thumbnail} = Astro.props;

---
<!DOCTYPE html>
<html lang="en">
	<head>
....
    <meta property="og:title" content={title} />
    <meta property="og:type" content="website" />
    <meta property="og:description" content={description} />
    <meta property="og:image" content={thumbnail.src} />
    <meta property="og:url" content={Astro.request.url} />
    <meta name="twitter:card" content="summary_large_image" />

....

このような感じでだいたいOKです。

外から確認するには?

ヘッダータグに組み込んだOGPのメタ情報は以下のサイトでチェックできます。

ここでサイトのURLを入れれば Twitterなどで埋め込まれるOGメタタグのプレビューができます!

Open Graph Meta Tags Preview

ただし当然ですが本番サイトでないとチェックできないです。。。

今回はここまでといたします。

Discussion