ブログ記事のリンクをXに貼り付けたところ、カード自体は表示されたのですが、OGP画像内の日本語が四角い文字で表示されていました。

ページ本文やタイトルの文字化けではなく、リンクカードに表示される画像の中だけがおかしい状態です。
最初はHTML側の文字コード指定やOGPのmetaタグ周りを疑ったのですが、実際に生成されている画像を確認すると、画像そのものの日本語が表示できていませんでした。
今回は、その原因と修正内容をメモしておきます。
起きていたこと
記事ごとに生成されるOGP画像で、日本語の部分だけが四角い文字になっていました。
たとえば、記事タイトルに日本語と英語が混ざっている場合、英語部分は表示されます。
一方で、日本語部分は□□□のように表示されてしまいます。
この時点で、HTMLの文字コードというより、画像生成時に使っているフォントが日本語に対応していない可能性が高そうです。
OGP画像の生成箇所
このブログでは、AstroPaperの仕組みを使って記事ごとのOGP画像を生成しています。
対象になっているのは、主に次のファイルです。
src/pages/posts/[...slug]/index.png.ts
src/utils/generateOgImages.ts
src/utils/og-templates/post.js
src/utils/loadGoogleFont.ts
記事ページ用のOGP画像は、src/pages/posts/[...slug]/index.png.tsから生成されます。
その中でgenerateOgImageForPost()が呼ばれ、最終的にsrc/utils/og-templates/post.jsのテンプレートを使って画像が作られます。
画像生成にはSatoriと@resvg/resvg-jsが使われています。
ざっくり言うと、次のような流れです。
- SatoriでHTML風のオブジェクトからSVGを作る
@resvg/resvg-jsでSVGをPNGに変換する- 生成したPNGをOGP画像として返す
原因
原因は、OGP画像生成時に使っていたフォントでした。
もともとの設定では、src/utils/loadGoogleFont.tsでIBM Plex Monoを読み込んでいました。
const fontsConfig = [
{
name: "IBM Plex Mono",
font: "IBM+Plex+Mono",
weight: 400,
style: "normal",
},
{
name: "IBM Plex Mono",
font: "IBM+Plex+Mono",
weight: 700,
style: "bold",
},
];
IBM Plex Mono自体は見た目のよい等幅フォントですが、日本語のグリフを持っていません。
そのため、Satoriで日本語を描画しようとしても表示できず、日本語部分だけ四角くなっていました。
ブラウザで表示する通常のHTMLであれば、別の日本語フォントにフォールバックされることがあります。
しかし、OGP画像生成ではSatoriに渡したフォントが重要になります。画像生成時に日本語を描画できるフォントが入っていなければ、そのまま文字欠けになります。
修正内容
日本語を含むOGP画像を生成するため、フォントをNoto Sans JPに変更しました。
src/utils/loadGoogleFont.tsを次のように修正します。
const fontsConfig = [
{
name: "Noto Sans JP",
font: "Noto+Sans+JP",
weight: 400,
style: "normal",
},
{
name: "Noto Sans JP",
font: "Noto+Sans+JP",
weight: 700,
style: "normal",
},
];
あわせて、OGP画像テンプレート側でもfontFamilyを明示しました。
style: {
fontFamily: "Noto Sans JP",
}
これで、Satoriに登録したフォント名と、テンプレート側で指定するフォント名が一致します。
固定テキストもフォント取得対象に含める
もうひとつ注意点がありました。
このブログのフォント取得処理では、Google Fontsにtextパラメータを渡して、必要な文字だけを含むフォントを取得しています。
const API = `https://fonts.googleapis.com/css2?family=${font}:wght@${weight}&text=${encodeURIComponent(text)}`;
この方式はフォントサイズを抑えられるので便利です。
ただし、textに含めていない文字はフォントに入らないため、テンプレート内の固定テキストも忘れずに含める必要があります。
たとえば、画像内にBLOG /やALPACA PRESSのような固定ラベルを表示する場合、それらの文字もフォント取得用の文字列に含めます。
const fontText = [
post.data.title,
post.data.author,
SITE.title,
category,
"BLOG / ",
"written by",
"ALPACA PRESS",
].join("");
これを忘れると、記事タイトルの日本語は直ったのに、固定ラベルの一部だけが四角くなる、という状態になります。
確認
修正後にビルドして、OGP画像が生成できることを確認しました。
npm run build
生成された画像は、次のようなパスに出力されます。
dist/posts/カテゴリ名/記事スラッグ/index.png
実際に生成されたPNGを開いて、日本語が四角くならず表示されていればOKです。
デザインも変更
ついでにデザインも変更しました。

まとめ
今回の原因は、HTML側の文字コードではなく、OGP画像生成時に使っていたフォントでした。
ポイントは次の3つです。
- Satoriで日本語を描画するには、日本語グリフを持つフォントを渡す
- テンプレート側の
fontFamilyと登録フォント名を合わせる - Google Fontsの
textパラメータを使う場合、固定ラベルの文字も含める
OGP画像はSNSに貼ったときに最初に見える部分なので、文字がちゃんと表示されるだけでもだいぶ印象が変わります。
今回のように画像内だけ文字化けしている場合は、HTMLのmeta charsetよりも、画像生成に使っているフォントを先に疑うとよさそうです。


