ブログの記事を友人にLINEで送ったら、リンクのプレビュー画像がこうなっていました。

タイトルの日本語が、ひとつ残らず「□」です。読めるのは「II」「3」「16」「825」「AI」といった英数字だけ。著者名もサイト名も□でした。
このブログはAstro(テーマはAstroPaper)で作っていて、OGP画像は記事ごとに自動生成しています。公開から5か月、全記事のプレビュー画像がこの状態だったのに、自分では一度も気づいていませんでした。
この記事では、原因と直し方をコード付きで書きます。同じ構成でブログを作っている人の参考になればうれしいです。
先に結論です。
- 原因は、OGP画像を作るsatoriに、日本語の字形を持つフォントを渡していなかったこと
- AstroPaperの初期設定は英字用のフォント(Google Sans Code)だけを読み込んでいる
- Google Fontsから、タイトルに使う文字だけのNoto Sans JPを取得して追加すれば直る(1枚あたり数十KB)
- フォントの取得に失敗してもビルドが止まらないようにしておくと安心
なぜ「□」になるのか
AstroPaperは、記事ごとのOGP画像をsatoriというライブラリで作っています。satoriは、HTMLとCSSの書き方で組んだレイアウトをSVGに変換するライブラリです。それをsharpでPNGにして、/posts/記事名/index.png として出力しています。
satoriは、ブラウザのようにパソコンに入っているフォントを勝手に使ってはくれません。使うフォントのデータを、コードの中で渡す必要があります。
AstroPaperの初期設定で渡しているのは、Google Sans Codeという英字用のフォントだけでした。
const fonts = fontData["--font-google-sans-code"];
// …(中略)…
fonts: [
{ name: "Google Sans Code", data: regularData, weight: 400, style: "normal" },
{ name: "Google Sans Code", data: boldData, weight: 700, style: "normal" },
],
Google Sans Codeには、ひらがなも漢字も入っていません。字形がない文字は、satoriが「□」(いわゆる豆腐)で描きます。英数字だけが読めたのはこのためです。
ブログの記事ページ自体は、ブラウザが日本語フォントで表示してくれるので普通に読めます。壊れていたのは、SNSやLINEに貼ったときのプレビュー画像だけでした。自分で記事を開いても気づけないのは、そのためです。
直し方:Noto Sans JPを「使う文字だけ」追加する
日本語フォントを追加すれば直ります。ただ、Noto Sans JPをまるごと入れると1ウェイトで数MBあり、リポジトリに置くには重すぎます。
そこで、Google Fontsの text パラメータを使いました。URLに文字を指定すると、その文字だけを含んだフォントを返してくれる機能です。タイトルに使う文字だけを取り寄せれば、1枚あたり数十KBで済みます。
手順1:日本語フォントを取得する関数を作る
src/utils/loadJapaneseFont.ts を新しく作りました。
export async function loadJapaneseFont(
text: string,
weight: 400 | 700
): Promise<ArrayBuffer | undefined> {
const chars = Array.from(new Set(Array.from(text))).join("");
const cssUrl = `https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@${weight}&text=${encodeURIComponent(chars)}`;
try {
const css = await fetch(cssUrl).then(res => res.text());
const fontUrl = css.match(
/src: url\((.+?)\) format\('(?:opentype|truetype)'\)/
)?.[1];
if (!fontUrl) return undefined;
return await fetch(fontUrl).then(res => res.arrayBuffer());
} catch {
// 取得に失敗してもビルドは止めない
return undefined;
}
}
ポイントは3つです。
- 重複した文字を除いてからURLに入れる(同じ文字を何度も送らない)
- Google FontsのCSSからフォントファイルのURLを抜き出して、もう一度取得する
- 取得に失敗したら
undefinedを返して、ビルドを止めない
3つ目は大事です。ビルド中にGoogle Fontsへつながらなかっただけで、デプロイ全体が失敗するのは避けたいからです。失敗した場合は、従来どおり英字フォントだけで画像が作られます。
なお、satoriはWOFF2形式に対応していません。私が試した範囲では、text パラメータ付きのリクエストにはTrueType形式(format('truetype'))が返ってきました。もしWOFF2しか返ってこない場合は、リクエストのUser-Agentを古いブラウザのものにすると、TrueTypeが返ってくることが多いようです。
手順2:OGP画像を作る処理でフォントを渡す
src/pages/posts/[...slug]/index.png.ts で、satoriに渡すフォントに追加します。
const ogText = `${title}# ${tag ?? ""}${author}${siteTitle}`;
const [jpRegular, jpBold] = await Promise.all([
loadJapaneseFont(ogText, 400),
loadJapaneseFont(ogText, 700),
]);
const fonts = [
...(jpRegular ? [{ name: "Noto Sans JP", data: jpRegular, weight: 400, style: "normal" }] : []),
...(jpBold ? [{ name: "Noto Sans JP", data: jpBold, weight: 700, style: "normal" }] : []),
];
ここで一度つまずきました。ogText に入れ忘れた文字は、取得したフォントに含まれないので、また□になります。私はタグの前に付けた「#」を入れ忘れていて、そこだけ□が残りました。画像に出す文字は、記号も含めて全部 ogText に入れるのがコツです。
サイト全体のOGP画像(src/pages/og.png.ts)も同じ作りだったので、同じように直しました。
ついでにデザインも作り直した
日本語が出るようになったものの、AstroPaperの初期デザイン(白い背景に黒い枠線)は、日本語の長いタイトルだと少し窮屈でした。せっかくなので、ブログのテーマ色に合わせて作り直しています。

変えたのは次のところです。
- 背景を紺のグラデーションにして、タイトルは白の太字
- タイトルの左にオレンジの縦線
- 左上に記事の最初のタグ、左下にプロフィール画像と著者名
- タイトルの長さで文字の大きさを変える(30字以下は64px、45字以下は56px、それ以上は50px)
最後の一つは、日本語ブログでは効きます。【】付きの長いタイトルが多いので、文字の大きさを固定すると4行目が切れてしまっていました。
英数字も含めてすべてNoto Sans JPにそろえたので、「AWS」「AdSense」のような英字も、日本語と同じ字形で表示されます。
確認するときの注意:LINEはプレビューをしばらく覚えている
修正をデプロイしても、一度LINEに貼ったリンクのプレビューは、すぐには新しい画像に変わりません。LINEやSNSは、OGP画像をしばらく保存しておくからです。
確認するときは、次のようにするのが確実でした。
- ビルドしたあと、
dist/posts/記事名/index.pngを直接開いて目で見る - 本番サイトで
https://ドメイン/posts/記事名/index.pngを直接開く
私は修正前に何枚か画像を開いて見比べ、□が残っていないことを確認してからデプロイしました。
まとめ
- AstroPaperのOGP画像で日本語が□になるのは、satoriに英字用のフォントしか渡していないから
- Google Fontsの
textパラメータで、使う文字だけのNoto Sans JPを取得して追加すれば直る - 画像に出す文字は、記号も含めて全部フォント取得用の文字列に入れる
- 取得に失敗してもビルドを止めない作りにしておく
- 記事ページは普通に読めるので、SNSに貼るまで気づけない。テーマを入れたら、一度プレビュー画像を直接開いて確認しておくべきでした
ブログを作るときのAIの使い方は、AIで開発と勉強はどれだけ速くなるかに書いています。同じくブログ運営でつまずいた話として、AdSenseに2回落ちた原因を分析した記事もあります。
関連記事
よくある質問(FAQ)
Q1. AstroのOGP画像で日本語が□になるのはなぜですか?
OGP画像を作るsatoriに、日本語の字形を持つフォントを渡していないためです。AstroPaperの初期設定は英字用のGoogle Sans Codeだけを読み込んでいるので、ひらがなや漢字は□(豆腐)で描かれます。
Q2. Noto Sans JPをまるごとリポジトリに入れてもいいですか?
動きますが、1ウェイトで数MBあるので重くなります。Google Fontsのtextパラメータを使えば、タイトルに使う文字だけを含んだフォントを取得でき、1枚あたり数十KBで済みます。
Q3. satoriで使えるフォント形式は何ですか?
TrueType(TTF)、OpenType(OTF)、WOFFです。WOFF2には対応していないので、Google Fontsから取得するときはTrueTypeが返ってきているか確認してください。
Q4. 修正したのにLINEのプレビューが□のままです。
LINEやSNSはOGP画像をしばらく保存しているため、一度貼ったリンクはすぐには更新されません。本番サイトでOGP画像のURL(/posts/記事名/index.png)を直接開いて、修正が反映されているか確認してください。
Q5. ビルド中にGoogle Fontsへつながらなかったらどうなりますか?
この記事のコードでは、取得に失敗するとフォントを追加せずに処理を続けるので、ビルドは止まりません。その場合は英字フォントだけで画像が作られ、日本語は□になります。