📸

satoriを使ってJSXからOGP画像を作成してみる

2023/03/28に公開

これは スペースマーケット Engineer Blog の記事です。
書き手はfumink7です。

BLUE GIANTのサントラを聞きながらこの記事を書いています。
最近見る映画が大当たりばっかりで楽しいですね〜🎥✨


OGP画像をコード(Node.js)上で簡単に作成したい

Webサービスで扱っている商品などを魅力を伝えるためにはOGP画像の作成なんかも必要になってくると思うのですが、jsxで簡単にデザインして画像として出力できそうだったので試しにやってみました。

サンプルとして出力した画像
作成したOGP画像サンプル

やったこと

Node.jsでsatoriを使ってJSXをSVGとして出力、sharpを使ってPNG画像に変換します。

他に詳しく解説されてる方いらっしゃるので、正直↓見ていただければこの記事は読まなくて大丈夫です()

https://zenn.dev/kou_pg_0131/articles/satori-usage
https://azukiazusa.dev/blog/satori-sveltekit-ogp-image/

  • 検証環境
    • Node.js
      • 16.11.1
    • パッケージ
      • satori
        • 0.4.2
      • sharp
        • 0.31.3

サンプルコード

import satori from 'satori'
import sharp from 'sharp'

// フォントファイルの読み込み
const fontData1 = fs.readFileSync('font/path1/NotoSansJP-Regular.otf')
const fontData2 = fs.readFileSync('font/path2/NotoSansJP-Bold.otf')
// fetchで外部のフォントファイルを利用することも可能らしいですよ!
// https://zenn.dev/kou_pg_0131/articles/satori-usage#%E5%9F%BA%E6%9C%AC

const svg = await satori(
  // 第1引数:レイアウト作成(JSX)
  // ひたすらインラインスタイルを当てていく
  <>
    <div
      style={{
        display: 'flex',
        width: '1200px',
        height: '630px',
        backgroundColor: '#DEF1F7',
        border: '2px solid #EAEAEA',
      }}
    >
      // 〜〜省略〜〜
    </div>
  </>,
  // 第2引数:設定
  {
    width: 1200,
    height: 630,
    fonts: [
      {
        name: 'Noto Sans JP',
        data: fontData1,
        weight: 400,
        style: 'normal',
      },
      {
        name: 'Noto Sans JP',
        data: fontData2,
        weight: 700,
        style: 'normal',
      },
    ],
  },
)

const pngBuffer = await sharp(Buffer.from(svg)).png().toBuffer()

// 画像を保存
fs.writeFileSync('image/path/ogp-sample.png', pngBuffer)

これでOGP画像を作成することができます📸

satoriのその他機能や注意点

satoriのページ見てもらえばよいのですが、せっかくなので知っておくと良さそうな情報を抜粋しておきます。

jsxじゃなくても作成できる

このようにObject形式で記述できます。

// リンク先より抜粋
await satori(
  {
    type: 'div',
    props: {
      children: 'hello, world',
      style: { color: 'black' },
    },
  },
  options
)

指定できるスタイルが限られている

インラインでスタイルを記述していくわけですが、指定できるスタイルが限られています。
ただほとんど必要なスタイルは揃っているので困ることはあまりなさそう。

絵文字を表示させる

絵文字はそのまま画像に出力することできないのですが、graphemeImagesを使って対応する画像を割り当てて表示させることができます。

// リンク先より抜粋
await satori(
  <div>Next.js is 🤯!</div>,
  {
    ...,
    graphemeImages: {
      '🤯': 'https://cdnjs.cloudflare.com/ajax/libs/twemoji/14.0.2/svg/1f92f.svg',
    },
  }
)

ただこの方法だと必要な絵文字が限られている場合じゃないと難しそうなのですが、loadAdditionalAssetを使えば動的に画像やフォントがない場合の処理を書けるようなのでどんな絵文字が使われるかわからない場合はこちらで対応できそう(という理解であっているのか?)。

フォントファイル指定しなくてもこれでシステムフォント利用できるのでは?(未確認)

// リンク先より抜粋
await satori(
  <div>👋 你好</div>,
  {
    // `code` will be the detected language code, `emoji` if it's an Emoji, or `unknown` if not able to tell.
    // `segment` will be the content to render.
    loadAdditionalAsset: async (code: string, segment: string) => {
      if (code === 'emoji') {
        // if segment is an emoji
        return `data:image/svg+xml;base64,...`
      }

      // if segment is normal text
      return loadFontFromSystem(code)
    }
  }
)

ロケールの設定が可能

ロケールの指定ができるので、文字化け等も回避できそうですね。

// リンク先より抜粋
await satori(
  <div lang="ja-JP"></div>
)

文字をテキストとしてSVGに埋め込む

デフォルトではテキストはSVGにpathとして埋め込まれるのですが、satori関数の第2引数にembedFont: falseを設定するとtextとして埋め込んでくれるそうです。

バウンディングボックスを確認する

satori関数の第2引数にdebug: trueを指定するとバウンディングボックスが確認できます。
…レイアウトがうまくいかない時とかに使えるかも?

debug: trueで出力

スペースマーケット Engineer Blog

Discussion