JavaScriptに関するお知らせ

SINCE2019
ホーム
>
【JS】OGPの画像生成をCloudinaryからSatoriにする

【JS】OGPの画像生成をCloudinaryからSatoriにする


こんにちは!SaaS離れの年頃のMizutani(@sirycity)です。なんか自分で作りたくなってきた。

というわけで今回はまさに今までSaaSのCloudinaryに頼っていたOGPの画像をSatoriを使って自力での実装に変更した話です。

結論

ビルドが重いのとレイアウトにちょっと制限がある以外は大満足!

はじめに

ブログがSNSでシェアされる時、画像もいっしょに表示されると嬉しいですよね?その設定をOGPって言います。OGPは文章だったり作者名だったり色々ありますが、今回はそのうち画像についてを扱います。以後OGPはOGPの画像のことを指します。

OGPについて

技術的な部分は割愛しますが要するにこんな形式で画像のURLを示すだけです。こんだけなら簡単。

<meta property="og:image" content="" />

動的OGPについて

でも実際のところOGPを場合によって変えたい時とかありますよね。ブログタイトルとか。そんな時に毎回画像作ってたら大変です。なのでパラメータを元に画像を自動で作る機能が欲しいわけです。これが動的OGP。

Cloudinaryについて

Cloudinaryは画像処理のSaaSです。まあGoogleフォトの豪華版みたいなもんです。S3の豪華版って言った方がいいかも。

んで、このCloudinaryを使うとURLパラメータに基づいてOGPを自動生成できるんです。まあ便利。設定は割愛します。というか設定したの3年前くらいで忘れた。ごめん…

Satoriについて

このパラメータを元に画像を自動で作る機能を叶えるjsのライブラリがSatoriです。しかもレイアウトをjsxで書けちゃう。すごいね。なおSatoriは厳密にはパラメータをsvgに変換するライブラリなので今回はそれをさらにpngに変換します。OGPはsvg使えんからね。

実際にやってみる

というわけで実際にやってみましょう。今回は最小構成をAstroでやってみます。なお、この記事を参考にしました。

Astroでsatoriを使ったOG画像の自動生成を実装する

パッケージ入れる

本体とsvg→png変換用。

npm i satori @resvg/resvg-js

コンポーネント作る

jsxファイル。超簡単に。最小構成だからな。

export const OgImage = ({ text }) => <div>{text}</div>

ロジック部分を作る

jsファイル。上の記事ほぼコピペ。

import { Resvg } from '@resvg/resvg-js'
import satori from 'satori'

// import { OgImage } from コンポーネント置いた場所

export const getOgImage = async text => {
  const fontData = await getFontData()

  const svg = await satori(OgImage({ text }), {
    width: 1200,
    height: 630,
    fonts: [{ name: 'Noto Serif JP', data: fontData, style: 'normal' }],
  })

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

  const image = resvg.render()
  const png = image.asPng()
  return png
}

const getFontData = async () => {
  const API = `https://fonts.googleapis.com/css2?family=Noto+Serif+JP:wght@700`

  const css = await (
    await fetch(API, {
      headers: {
        'User-Agent':
          'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; de-at) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1',
      },
    })
  ).text()

  const resource = css.match(/src: url\((.+)\) format\('(opentype|truetype)'\)/)

  if (!resource) return

  return await fetch(resource[1]).then(res => res.arrayBuffer())
}

適当に記事一覧つくる

tsファイル。適当にこんなかんじに。

export const posts = [
  { slug: '2023-01-01', title: 'あけおめ' },
  { slug: '2023-12-25', title: 'メリークリスマス' },
]

動的ページの読み込み部分作る

Astroの場合はこう。 src > pages > 動的ページ みたいなかんじ。

touch src/pages/\[slug\].png.ts

中身はこう。

// import { posts } from 記事一覧置いた場所
// import { getOgImage } from ロジック置いた場所

export const getStaticPaths = async () =>
  posts.map(({ title, slug }) => ({ params: { slug }, props: { title } }))

export const GET = async ({ props: { title } }) => {
  const ogImage = await getOgImage(title)
  return new Response(ogImage, { headers: { 'Content-Type': 'image/png' } })
}

以上。実際にメタタグでOGP指定する所はまあええな。

Satoriの問題点

CSSに制限がかかる

例えばdisplayがflexしか使えんとか文字の中央寄せができんとか。この2つが特に気になった。細かい所だとグラデーションできんとか。まあ言い出したらきりがない。

フォントもだいぶ制約される

フォントデータはどっかのCDNから持ってこなきゃいかん。Webみたいに汎用フォントへのフォールバックができないです。

重い

重い。仕方ないけどね。ブログで使うとビルド時間が記事に比例して伸びていく。

さいごに

課題はそこそこあれど中途半端に使っていたSaaSから卒業できたのはとても嬉しい。こうやって成長していくんやな(自意識過剰)以上。



YouTube Liveを登録者数50人以下スマホ無しカメラ無しでまともに行う方法の考察
PREV
2024-09-08
YouTube Liveを登録者数50人以下スマホ無しカメラ無しでまともに行う方法の考察