blog.berlysia.net-HonoXでsatoriを使ってOGイメージもSSGする
avatar

HonoXでsatoriを使ってOGイメージもSSGする

  • meta
  • framework

このサイトをHonoXに書き換えたのですが、そういえばOGイメージがないことに気づきました。

どうやって画像を生成するか

今回はOGイメージもSSGしてしまうことにしました。記事数が増えてきて時間がかかるようになってきたら、ダイナミックレンダリングするように変えるかもしれません。

いったんSSGですませるのは、HonoXからCloudflare PagesのAdvanced ModeをSSGと両立していい感じにデプロイする方法がまだいい感じではないからです。何とかしようとは思っています。

satoriとresvgを使ってHonoXでビルドしたい

OGイメージを動的に生成する道具としては @vercel/og が有名どころです。これは内部的に satoriResvg を使っています。

Open Graph (OG) Image GenerationのLimitationsの項目に記載があるように、Node.jsや他のランタイムで動作させたい場合はsatoriを直接使うことが勧められています。

素朴に導入してみると盛大にエラーが出ますので先行事例をあたると、HonoXと同様にViteに乗っているAstroの事例として、satoriを使ったAstroのOGP画像生成メモ | Marginaliaがありました。

いろいろ細かい調査を省いて、 ssr.externalsatori @resvg/resvg-js を追加すると、SSGのコマンドが完走できるようになりました。

satori は単に依存にCommonJSが含まれていました。HonoXでは ssr.noExternal がtrueなので、SSRビルドでも明示的に指定する必要があります。

@resvg/resvg-js はネイティブモジュールがViteの事前ビルドに失敗します。上記記事に書かれているのと同じ問題でした。

バイナリをHonoのSSGヘルパーで出力すると壊れる問題があった(Hono v4.0.8で修正済み)

開発用サーバーだと画像が表示できるのですが、SSRした画像が開けない問題がありました。Resvgを通さずにSVGファイルとして出力すると問題がなかったので当初はResvgを疑ったのですが、開発用サーバーで動くことを思うと説明がつきません。

PNGファイルとして書き出していたので、PNGファイルのヘッダーを念のため確認してみると、各所に書かれている 89 50 4E 47 0D 0A 1A 0A とは異なっていました。

> hexdump -C generated/from-ssr/binary.png
00000000  89 50 4e 47 0d 0a 1a 0a  00 00 00 0d 49 48 44 52  |.PNG........IHDR|
00000010  00 00 00 01 00 00 00 01  08 02 00 00 00 90 77 53  |..............wS|
00000020  de 00 00 00 0c 49 44 41  54 78 9c 63 f8 cf c0 00  |.....IDATx.c....|
00000030  00 03 01 01 00 c9 fe 92  ef 00 00 00 00 49 45 4e  |.............IEN|
00000040  44 ae 42 60 82 00 00 00  2f                       |D.B`..../|
00000049

> hexdump -C generated/from-ssg/binary.png
00000000  ef bf bd 50 4e 47 0d 0a  1a 0a 00 00 00 0d 49 48  |...PNG........IH|
00000010  44 52 00 00 00 01 00 00  00 01 08 02 00 00 00 ef  |DR..............|
00000020  bf bd 77 53 ef bf bd 00  00 00 0c 49 44 41 54 78  |..wS.......IDATx|
00000030  ef bf bd 63 ef bf bd ef  bf bd ef bf bd 00 00 03  |...c............|
00000040  01 01 00 ef bf bd ef bf  bd ef bf bd ef bf bd 00  |................|
00000050  00 00 00 49 45 4e 44 ef  bf bd 42 60 ef bf bd 00  |...IEND...B`....|
00000060  00 00 2f |../|
00000063
hexdumpの結果

雑に壊れているPNGの冒頭のバイト列で検索してみると、node.js - Unexpected buffer result when fetching PNG image - Stack Overflowのやり取りを見つけました。バイナリを誤ってUTF-8として解釈してしまっているという話のようです。

なるほど、 ef bf bd はUTF-8のコードポイントU+FFFD(replacement character)にあたります。UTF-8として解釈したときにうまくいかないバイト列があるときに現れます。こうなってくると、SSGの処理に問題がありそうです。

hono/ssg のヘルパーに露骨にバイナリをUTF-8扱いして文字列化する処理が含まれていたので、honojs/hono #2275を出して修正しました。

ついでにBudouXで改行位置をいい感じにする

いい感じの折り返し制御のために、BudouX を使いました。説明は他所に譲ります。これも ssr.external に追加する必要がありました。

実装はBudouXとSatoriを使ってタイトルが分かち書きされたOGP画像を出力する。 - return $lock;が参考になりました。

OGイメージの例
OGイメージの例

まとめ

HonoXでOGイメージをSSGするためにsatoriとresvgを使って実現しました。Viteの上でビルドするためのHonoX独特の設定が必要なのと、HonoのSSGヘルパーに問題があったので修正しました。皆さんもお試しください。

参考文献