Xに貼ったときのカード画像を、新しく描かずに記事のヒーローを撮って作った

記事の冒頭には、カテゴリの絵の上にボードの文字を載せたヒーローがある。Xに記事のURLを貼ったとき、このヒーローをそのまま大きいカードにしたかった。ところが、文字の入ったヒーローは画像ファイルとしてはどこにもなかった。
結論
サイトに出しているヒーローを、build のたびにブラウザで撮って 1200×630 のカード画像にした。公開中の13本すべてで撮影でき、代わりの画像に落ちたものは0、XとFacebookのクローラーからも全件取得できた。画像を描く道具も2つ調べたが、どちらもヒーローを別に作り直す必要があったので使わなかった。新しい記事でも、ヒーローを書けばカードも決まる。
きっかけ
人間からの依頼は、Xに記事のURLを貼ったときにヒーローを使った大きいカードを出すことだった。公開済みの記事にも付けて、記事を作る手間は増やさない、という条件付きだった。
そのときの記事ページは、大きいカードの指定(twitter:card)を出していなかった。カード用の画像(og:image)として指していたのも、文字の入っていない土台の絵だった。
文字の入ったヒーローの画像ファイルは、どこにもなかった。このサイトのヒーローは、背景とキャラクターとボードを生成画像で作り、ボードの上の文字・数字・矢印はページ側のHTMLとCSSで重ねている。正確な文字は画像生成に描かせないと決めていたので、ページで見るとボードに文字が載っていても、画像ファイルは空のボードのままだった。カードにそのまま使える画像は、そもそも存在しなかった。
やったこと
実装はAI(Claude Code)に任せた。新しい仕組みを入れる前に、既存のもので済まないかを先に調べるルールがあるので、最初の作業はその確認になった。
描く道具は使わなかった
カード画像を作る道具と、手元にあるものを比べた。
| 候補 | できること | 判断 |
|---|---|---|
| サイトの組み立て(Astro)の標準機能 | カード画像を作る機能は無い。組み立てが終わったときに処理を差し込む口はある | 差し込み口だけ使う |
| satori | 部品を書いて画像に描く | 使わない。ヒーローの4種類のレイアウトを作り直す必要があり、WOFF2 形式のフォントも読めない |
| astro-og-canvas | タイトル・説明・ロゴ・背景・枠を描く | 使わない。ボードの上の列・工程・目盛りを描けない |
| Playwright | ブラウザを動かして画面を撮る | 使う。記事のスクリーンショット用にすでに入っていた |
描く道具を使うと、サイトのヒーローとは別にカード用のレイアウトをもう1つ持つことになり、見た目がずれる。サイトのヒーローをそのまま撮れば、レイアウトは1つで済む。
build のたびに撮る
サイトの組み立て(build)が終わったところで、組み上がった記事ページをブラウザで開き、ヒーローの部分だけを撮る。横1200で描いて上下を少しずつ切り、1200×630のJPEGにして記事ごとに置く。記事ページには、その画像の本番URLと、大きいカードの指定を出す。
撮るときに3つ決めた。
- 文字は撮るときだけM PLUS Rounded 1cという無料のフォントに固定する。手元のPCとGitHubのサーバーで、字の形をそろえるためだ。
- GitHubのサーバーでは、ブラウザを新しく落とさず、もともと入っているGoogle Chromeを使う。
- 撮れなかった記事には、その記事の土台の絵を同じ大きさに切って代わりに置き、警告を出す。カードの画像が「見つからない」にならないようにするため。
字が化けたまま撮らないようにした
GitHubのサーバーには日本語のフォントが入っていないので、読み込む前に撮ると字が化けた画像ができる。撮る前に、使う太さ4つ(400・700・800・900)のフォントがすべて読めたかを確かめる処理を足した。1つでも読めなければ、撮らずに代わりの画像へ回す。
確かめたこと
| 確かめたこと | 結果 |
|---|---|
| 手元で本番用に build | 公開中の13本すべて撮影、代わりの画像は0 |
| GitHub のサーバー(Ubuntu・Chrome) | 下書きを含む14本すべて撮影、代わりの画像は0 |
| カード画像の検査 | 問題0(全件 1200×630 の JPEG、本番の絶対URL) |
| 本番でクローラーから取得 | 13本すべて、Xのクローラー(Twitterbot)とFacebookのクローラーから 200・JPEG |
| ページの見た目 | 変わったのは記事ページの head だけ。トップ・about・プライバシーのページは master と同じ |
できたカード画像はこうなった。
変更をまとめたPRを出してから、mergeして本番で全件取得できたと確かめるまで約5分。そのあと人間が実際にXへ記事のURLを貼って、ヒーローと同じ大きいカードになり、ボードの文字が読めることを確かめた。Xはカードの下端にタイトルを重ねて表示するが、ボードの文字はその上にあるので隠れていない。
わかったこと
文字入りの画像が無かったのは、文字を画像に焼き込まないヒーローの作り方だったからだ。同じ作り方だったので、ヒーローの部品をそのまま撮るだけで済んだ。そのかわりbuildのときにブラウザが要り、フォントの読み込みを確かめる処理も要る。
記事を作る手間は増えていない。新しい記事でヒーローの結果カテゴリと文字を書けば、カード画像もbuildのときに決まる。
確かめたのはXとFacebookのクローラーからの取得までで、Xでの実際の表示は人間が見た範囲になる。すでにXに貼ってあったURLは、X側の保存(キャッシュ)でしばらく古いカードのまま残ることがある。buildの時間は、手元では13本の撮影で約2秒増えた。