4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[Node.js]Satoriを用いて動的にOGPを生成する際のtips

Last updated at Posted at 2024-10-23

TL;DR

  • SatoriによるSVG生成にはフォントファイルの読み込みが必要
  • フォントファイルの読み込みはモジュールバンドラーとの相性が良くない
  • vite-plugin-arraybufferを利用してインラインでフォントファイルを読み込むことですっきりビルド

動的OGP生成の手法

筆者所属のMIERUNEにて最近QGIS LABというWebサイトをリリースしました。

このサイトの記事ページでは、記事タイトルから動的にOGP画像を生成しています。

動的OGPの生成は下記の手順がポピュラーのようです。

  1. vercel/satoriで、SVGを生成する(JSXによる記述が可能)
  2. SVGを画像化(Node.jsならlovell/sharp、それ以外だとyisibi/resvg-jsも利用できそうです)

これらの利用方法は以下のWebサイトが詳しいです。

satoriにはフォントファイルが必要

satoriはSVG生成時の第二引数でオプションを設定するのですが、そのオプションにはフォントの指定が必須です。フォントデータはBuffer型である必要があります。サンプルコードによれば、fsfetchで読んできてねと書いてあります。

import satori from 'satori'

const svg = await satori(
  <div style={{ color: 'black' }}>hello, world</div>,
  {
    width: 600,
    height: 400,
    fonts: [
      {
        name: 'Roboto',
        // Use `fs` (Node.js only) or `fetch` to read the font as Buffer/ArrayBuffer and provide `data` here.
        data: robotoArrayBuffer,
        weight: 400,
        style: 'normal',
      },
    ],
  },
)

動的に作るので、外部からフォントファイルをダウンロードする時間も節約したいので、fetchを利用する線はなくなります。

モジュールバンドラーが介在する場合の問題

fsで静的なファイルを読み込むと、以下のようなコードになります。

const font = fs.readFileSync('./fonts/NotoSansJP-Regular.otf')

このときファイルパスを指定するのですが、このパスはスクリプト実行時点のカレントディレクトリから見た相対パス(あるいは絶対パス)となります。

今回のWebサイトはSvelteKitで構築されているため、ソースコードとビルドアーティファクトのディレクトリ構成は全く異なります(なので相対パスで書きづらい)。また、実行環境に依存したコードもあまり書きたくないので、絶対パスもなるべく利用したくありません。

解決策:ビルド時にファイルをインラインに読み込んでしまう

SvelteKitではビルドにViteを利用するので、Viteのプラグインを利用してフォントファイルをimportすることで、バイナリデータをインラインで持ってしまうという策です。これによりシンプルに解決できて、以下がサンプルコードです。

import NotoSansJpBold from './NotoSansJP-Bold.ttf?arraybuffer&base64';
// "?arraybuffer"というsuffixで、vite-plugin-arraybufferがファイルをバンドルしてくれる
// "&base64"により、base64文字列として読まれる(今回の実装では必要だった)

const svg = await satori(
    <div>...</div>,
    {
        width: 1200,
        height: 630,
        fonts: [
            {
                name: 'Noto Sans JP Bold',
                data: NotoSansJpBold
            }
        ]
    }
);

// svg to webp
const buffer = await sharp(Buffer.from(svg)).webp().toBuffer();
return buffer;

これをビルドすると以下のようなコードになります。base64文字列がインラインに埋め込まれていることがわかりますね。

const NotoSansJpBold = decode64("AAEAAAATAQAABAAw...")

まとめ

  • この手を試す前はsapphi-red/vite-plugin-static-copyを用いてビルドアーティファクトにフォントファイルをコピーするような手も試しました。この手でも頑張れば解決は出来ると思いますが、実行環境に依存したコードが増えてポータビリティが低下すると思います(例:今回で言うと、AWS AmplifyとAWS Lambdaで動く必要があった)
  • バンドラーにViteを利用しているなら、今回紹介したプラグインが有効なケースは結構あるかもしれません
4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?