Xのリンクカードに画像が出ない原因を調べたら、featured_imageだけでは足りなかった

読了 約7分
Xのリンクカードに画像が出ない原因を調べたら、featured_imageだけでは足りなかった

こんなブログでも流入数を多少は増やしていきたいなという、気持ちで、試しに記事をXに投稿したとき、リンクカードに画像が出ませんでした。

リンクカードというのは、XにURLを貼ったときに自動で表示される、タイトルと画像がセットになったあのプレビューのことです。ちゃんと出ると見栄えがいいし、クリックされやすくなる気がして、できれば表示させたいやつです。

記事ページ自体を開くとアイキャッチ画像はちゃんと表示されていました。なのにXのカードには画像がない。「またX側の気まぐれか?」とも思ったのですが、せっかくなので原因をちゃんと調べることにしました。

まずHTMLのソースを確認しました

Webページの裏側にはHTMLというファイルがあって、その中に「この記事のSNSカード用画像はこれです」という情報を埋め込む仕組みがあります。og:imagetwitter:image といった「メタタグ」と呼ばれる記述がそれにあたります。

まずそのメタタグが正しく出力されているかを確認しました。Hugo(このブログで使っている静的サイトジェネレーター)でサイトを一時的にビルドして、出力されたHTMLを調べました。

hugo --destination /private/tmp/keyuki-og-check --environment production
rg -n "og:image|twitter:image|twitter:card" /private/tmp/keyuki-og-check/posts

結果を見て、最初は「あ、タグ自体は出てる」と思いました。必要なメタタグは全部ありました。

ところがよく見ると、どの記事もまったく同じURLを指していました。

<meta property="og:image" content="https://blog.keyuki.net/images/default-og.jpg" />
<meta name="twitter:image" content="https://blog.keyuki.net/images/default-og.jpg" />

記事ごとの画像ではなく、全記事共通の default-og.jpg という1枚の画像を指していました。

「メタタグがない」のではなく、「メタタグはあるけど、指している画像が全部同じ共通画像だった」という状態でした。

このブログはHugo + Anankeというテーマで動いています。記事を書くとき、ファイルの先頭に設定を書く欄(「フロントマター」と呼びます)があって、そこにアイキャッチ画像のURLを指定していました。

featured_image: https://images.keyuki.net/uploads/2026/05/example-eyecatch.png

これで記事ページにはちゃんと画像が表示されます。ところがXのリンクカードはここを見ていませんでした。

Hugoには featured_image とは別に、SNSカード用のメタタグを生成するための images という設定項目があります。HugoのOGP(Open Graph Protocolの略で、SNSにURLを貼ったときの見え方を制御する仕組みです)は、featured_image ではなく images を参照して動きます。

  • featured_image → 記事ページ上のアイキャッチ表示に使われる(Anankeテーマ固有の機能)
  • images → SNSカード用メタタグの生成に使われる(Hugo本体の機能)

images を設定していなければ、Hugoはサイト全体のデフォルト画像(default-og.jpg)で埋めます。今回まさにその状態でした。

画像サイズにも落とし穴がありました

共通の default-og.jpg を確認してみたら、解像度が 7200x4800、ファイルサイズが約7.6MBもありました。

Xのリンクカードには画像のサイズ制限があります。大きすぎる画像はカードに表示されません。共通画像はその条件を超えていたため、仮にフォールバックとして読み込まれていたとしても表示されなかった可能性が高いです。

SNSカード用の画像は 1200x630(横1200px × 縦630px)のJPEGで、ファイルサイズは数百KB以内に抑えるのが安全とされています。

修正した内容

各記事のフロントマターに images を追加しました

公開済みの記事のフロントマター(ファイル先頭の設定欄)に images を追加しました。SNSカード用の画像はアイキャッチとは別に、1200x630で作り直したものを使っています。

featured_image: https://images.keyuki.net/uploads/2026/05/example-eyecatch.png
images:
  - https://images.keyuki.net/uploads/2026/05/example-og.jpg

これで featured_image(記事ページ用)と images(SNSカード用)を分けて管理する形になりました。

SNSカード用の画像をR2にアップロードしました

各記事用のOG画像を1200x630のJPEGで作り、Cloudflare R2(このブログの画像置き場として使っているクラウドストレージです)にアップロードしました。

  • 形式: JPEG
  • サイズ: 1200x630
  • ファイルサイズ: 80KB〜193KB程度

共通の default-og.jpg も差し替えました

記事に images を設定し忘れたときのフォールバック用として、サイト全体の共通画像も軽量化して差し替えました。

  • 変更前: 7200x4800 / 約7.6MB
  • 変更後: 1200x630 / 約248KB

新規記事テンプレートにも例を追加しました

今後また忘れないよう、新しい記事を作るときの雛形(archetype)に images の書き方をコメントとして入れておきました。

なお、images: だけ書いてURLを空にした状態はHugoが空リストと解釈することがあるので、設定するときは必ずURLを入れた状態にします。

検証しました

Hugoで一時ビルドしてメタタグを確認:

hugo --destination /private/tmp/keyuki-og-check --environment production
rg -n "og:image|twitter:image|twitter:card" /private/tmp/keyuki-og-check/posts

今度は各記事がそれぞれの *-og.jpg を指していました。記事ページ本文の画像は従来通り featured_image の画像のまま変わっていません。

画像URLが実際に開けるかも確認しました:

curl -I --max-time 10 "https://images.keyuki.net/uploads/2026/05/nasa-clean-air-plants-og.jpg"

curl -I はURLにアクセスして「ちゃんと存在するか」「何のファイルか」だけを確認するコマンドです。HTTP 200(正常)/content-type: image/jpeg(JPEGとして認識されている)が返ってきたことを確認しました。XのクローラーであるTwitterbotのUser-Agentでも同様にアクセスできることを確認しました。

X側にキャッシュが残っている場合、修正後すぐ反映されないことがあります。カード検証ツールで再クロールをリクエストすると反映が早まる可能性があります。

学んだこと

今回の一番の学びは「記事に画像が出ていること」と「SNSカードに画像が出ること」は別の話、ということです。

記事ページで見えているアイキャッチは「テーマが表示している画像」で、SNSカードに使われる画像は「HugoのOGP機能が別途参照する画像」でした。設定する場所が違います。

さらに「画像URLが存在する」だけでも不十分で、SNSカードのサイズ要件(1200x630、5MB未満あたり)を満たしていないと表示されないことがあります。

自分でブログを一から運用していると、こういう設定の細かい違いは全部自分で気づいて対処しないといけません。過去に書いていたブログはWordPress だったのですが、そんなことしなくてよかったので、SaaSなら、こういう部分は全部よしなにやってもらえるんだよな、と思い、SaaSのありがたみをじんわり感じました。 改造していくのも楽しいですけどね。