BLOG /

Next.jsでOGP画像を記事ごとに自動生成する方法とハマりどころ

next/ogのImageResponseで記事ごとにOGP画像を自動生成する手順と、フォント形式やCSSの制約でビルドが失敗する落とし穴、その回避策を解説します。

Next.jsで記事ごとにOGP画像を自動生成しようとすると、ビルドが途中で止まることがある。next/ogのImageResponseは便利な機能だが、内部はVercelの画像生成処理とsatoriという描画エンジンで動いている。この仕組みを知らずに実装すると、フォント形式やCSSの書き方でエラーが出る。起こりやすい3つの落とし穴と、それぞれの回避策を整理する。

next/ogのImageResponseで記事ごとにOGP画像を自動生成する仕組み

App Routerには、opengraph-image.tsxというファイルを置くだけで専用のOGP画像を生成する機能がある。中身はnext/ogが提供するImageResponseというクラスだ。JSXでレイアウトを書き、それを画像として書き出す。記事一覧のように複数ページがある場合、記事ごとにタイトルやURLを差し込んで、1ページ1画像を自動生成できる。

内部を分解すると、ImageResponseは@vercel/ogを経由し、その先でsatoriというライブラリがJSXをSVGに変換している。satoriはブラウザのレンダリングエンジンではない。CSSの解釈範囲もフォントの扱いも独自のルールを持つ。ここを意識せずに実装すると、ビルドが通らなかったり、レイアウトが崩れたりする。

フォントはOTF/TTF必須、woff2を使うとビルドが落ちる

日本語フォントを埋め込もうとして@fontsource系のパッケージを使うと、ビルド時に次のエラーが出ることがある。

Error: Unsupported OpenType signature wOF2

satoriが受け付けるのはOTFとTTFで、woff2は対象外。@fontsource系のパッケージはwoff・woff2しか持っておらず、そのままではImageResponseのfontsオプションに渡せない。

回避策は、OTF形式のフォントファイルを別に用意すること。Noto Sans JPなら、GitHubのnotofontsリポジトリで配布されているsubset OTF版で解決できる。取得したOTFはpublic配下ではなく、ビルド専用のディレクトリに置く。readFileSyncでバイナリとして読み込み、fontsに渡す。こうすればビルド時にだけ使われ、出力ファイルには含まれない。

satoriが理解するCSSは一部だけ、レイアウト崩れを防ぐ書き方

satoriはCSSの全プロパティを解釈しない。insetのような省略記法は効かないことがあり、top・right・bottom・leftを個別に指定したほうが無難だ。

もうひとつ見落としやすいのが、子要素が複数あるdivタグの扱い。displayをflexで明示しないと、レイアウトは大きく崩れる。satoriはブロック要素の自動整列をあてにできない。指定を省くと、要素が重なったりはみ出したりする。

位置調整をtransformのtranslateYで細かく積み上げるより、flexとjustifyContent・alignItemsで素直に中央寄せするほうが安定する。全体をヘッダー・中央・フッターの3段構成にして、それぞれをflexで組むと崩れにくいテンプレートになる。

Google Fontsをfetchする方式は詰まりやすい、ローカルOTFが安定

OGP画像生成のサンプルでよく見るのが、Google FontsのCSSをビルド時にfetchして使う方式だ。だがGoogle FontsのCSS2は、フォントを複数のwoff2ファイルに分割して配信している。ImageResponseのfontsオプションには単一のバイナリしか渡せず、この方式とは相性が悪い。

動いているように見えても、実際には一部の字形が読み込まれず欠けることがある。エラーメッセージには表れないため、見た目だけでは原因を判別しづらい。

回避策はシンプルだ。最初からローカルにOTFファイルを置いておく。fetchに頼らない構成なら、ネットワーク環境やAPIの応答状況に結果が左右されない。記事ページが増えても、フォント周りのエラーに悩まされにくくなる。


OGP画像は地味な機能に見えて、SNSでの見え方を大きく左右する。フォントとCSSの制約を先に知っておくだけで、次の実装はかなり楽になるはずだ。同じ落とし穴にぶつかっている人は、他にもいるかもしれない。制作のご相談はサービス内容から、ほかの制作記録はブログにまとめている。