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人以下スマホ無しカメラ無しでまともに行う方法の考察

スマホにケースを付けずフィルムも貼らず割れても気にしない理由をよく聞かれるので書いておく
NEXT
2024-09-23
スマホにケースを付けずフィルムも貼らず割れても気にしない理由をよく聞かれるので書いておく