SNSでシェアしても画像が出ないときの確認順|og:imageの404・外部ホスト・キャッシュを上から潰す
SNSでシェアしても画像が出ないときの確認順|og:imageの404・外部ホスト・キャッシュを上から潰す
LINEでURLを送ったのに、画像のないそっけないリンクになる。Xに貼っても文字だけ。この症状の相談は多く、そしてほとんどの人が最初に疑う場所を間違えています。
結論から書きます。確認は次の順番で、上から降りてください。
- og:imageタグがHTMLに出ているか
- その値が絶対URLで、叩くと200が返るか
- その画像を置いたホストが、クローラーに開放されているか
- キャッシュ
多くの場合、いきなり4番目のキャッシュを疑って再取得を繰り返すことになります。しかし1〜3が壊れていれば、何回再取得しても画像は出ません。キャッシュは「正しい状態が反映されないとき」の話だからです。
この記事では、各段階の判定コマンドと、実案件で出た事例・実測値を書きます。
—
1. 確認は4段。順番を飛ばさない
先に全体像を置きます。
| 段 | 何を見るか | 判定コマンド | NGだったときの症状 | |
|---|---|---|---|---|
| 1 | タグがHTMLに出ているか | `curl -s URL \ | grep 'og:image'` | 何も出ない/全ページ同じ画像 |
| 2 | 絶対URLで200か | curl -o /dev/null -w "%{http_code}" 画像URL |
404・403、または画像だけ出ない | |
| 3 | 画像ホストがクローラーに開放されているか | curl -s https://画像のホスト/robots.txt |
200なのに出ない(最も厄介) | |
| 4 | キャッシュ | 各SNSのデバッガー/クエリ付与 | 直したのに古い画像が出続ける |
1段目から順に潰していけば、大半は5分で切り分けられます。
—
2. 第1段:タグがHTMLに出ているか
ブラウザで見た目を確認しても判定できません。クローラーが受け取るのは、JavaScriptを動かす前のHTMLだからです。必ずcurlで確認します。
curl -s https://example.com/blog/xxxx/ | grep -o '<meta property="og:[^>]*>'
自社ブログの記事で実際に叩いた結果です。
<meta property="og:type" content="article">
<meta property="og:title" content="(記事タイトル)|graciauto">
<meta property="og:url" content="https://graciauto.jp/blog/xxxx/">
<meta property="og:image" content="https://graciauto.jp/og/1994.jpg">
ここで何も返らなければ、原因は1段目で確定です。JavaScriptで後から<head>に差し込む実装になっている場合は、サーバー側が返すHTMLの時点で出力するよう変えます。SPA構成をCMSのテーマに載せている場合は、PHP側で</head>の直前に挿入するのが確実です。
全ページ同じ画像・同じタイトルになっていないか
タグが出ていても、全ページで中身が同一なら実質的に機能していません。どのページをシェアしても同じカードが出るため、記事を出し分けているつもりが1種類しか届かない状態です。
これは共通テンプレートやSPAで起きやすい構造的な問題です。判定は簡単で、2〜3ページ分をcurlして並べるだけ。全部同じなら確定します。
注意点として、トップページだけを見て確認を終えると成功に見えます。実際に、React製のサイトをCMSテーマ化した構成で全ページが同じHTMLを返しており、7ページすべてでtitle・description・OGPが一致していた案件がありました。リクエストされたパスをキーに、サーバー側で出し分ける実装が必要になります。
—
3. 第2段:絶対URLで、200が返るか
og:imageに書く値は、スキーム(https://)から始まる絶対URLにします。次はいずれも正しく取得されません。
| 書き方 | 例 | 問題 |
|---|---|---|
| 相対パス | assets/ogp.jpg |
クローラー側の基準URLが不定 |
| ルート相対 | /assets/ogp.jpg |
配置場所が変わると404 |
| プロトコル省略 | //cdn.example.com/ogp.jpg |
取得できないクローラーがある |
3つ目は、CMSやECのテンプレート関数が返すURLをそのまま使うと起きます。関数の戻り値にhttps:が含まれているかを確認し、無ければ付ける処理を入れておくのが安全です。
ルート相対パスが危ないのは「公開場所が変わる」から
制作中は問題なく見えていたのに、公開したら画像だけ出ない——このパターンの多くはルート相対パスです。
たとえばLP制作では、確認用プレビューをサブディレクトリ(/pv-xxxxxxx/のような場所)に置くことがあります。このときog:imageが/assets/ogp.webpのようなルート相対で書かれていると、プレビュー環境ではドメイン直下を探しにいって404になります。本番公開すれば直りますが、「公開するまで正しさを確認できない」状態です。
対処は、本番公開の直後に画像URLを叩いて200を確認する工程を、公開手順そのものに書いておくこと。進行中のLP案件でも、この確認を残タスクとして明記しています。公開して終わりにせず、確認までを1セットにする設計です。
画像形式は一括変換の巻き添えに注意
表示速度対策で画像をまとめてWebPに変換するケースがあります。実案件では写真素材のWebP化で転送量を16.8MB→0.72MBまで落とせた例があり、効果の大きい施策です。
ただしog:imageだけはJPEG/PNGで持っておくのが無難です。SNSのクローラーは通常のブラウザとは別実装で、対応形式が同じとは限らないためです。一括置換のスクリプトを走らせるときは、og:imageの参照を除外対象にしておきます。
—
4. 第3段:画像ホストがクローラーに開放されているか
ここが最も見つけにくく、被害が大きい段です。
症状の特徴は、画像URLを叩くと200が返るのに、シェアカードにも検索結果のサムネイルにも出ないこと。ファイルは確かに存在するので、ブラウザで開いて確認しても異常が見つかりません。
原因は、画像を置いたホスト側のrobots.txtでクローラーが遮断されていることです。判定は1コマンドです。
curl -s https://(og:imageのホスト部)/robots.txt
ここでDisallow: /が返ったら確定です。
実例:記事168本分のシェア画像が一括で無効だった
ヘッドレス構成(表示用サイトと、記事を管理するCMSがドメイン単位で分かれている構成)では、この事故が構造的に起きます。
自社ブログで実際にあったのが、表示側のog:imageが管理用CMSドメインのアップロードURLをそのまま指していたケースです。管理側は検索結果に出したくないのでrobots.txtを全面Disallowにしてありました。つまり、
- 画像そのものは200で配信されている
- しかしクローラーはそのホストを読みにいけない
- 結果、記事168本すべてでシェア画像・検索サムネイルが機能していなかった
実測すると、管理側はDisallow: /、表示側はAllow: /。同じ会社のサイトでありながら、画像の置き場所だけが遮断側に残っていた状態です。
注意したいのは、画像の自己ホスト化は「固定ページ」と「記事」で処理経路が別になりやすい点です。片方だけ対応が済んで、もう片方が旧ドメイン参照のまま残る。移行作業では、対象範囲を投稿タイプ単位で数えて確認します。
対処は自己ホスト化。CMS側を開ける案は採らない
この状況では2つの選択肢があります。
| 案 | 内容 | 判断 |
|---|---|---|
| A | 管理CMS側のrobots.txtを緩める | 不採用 |
| B | 表示ドメインに画像をコピーして自己ホスト | 採用 |
Bを選んだ理由は2つです。
- 管理CMSは将来の置き換え候補。画像をそのドメインに依存させると、移行時に全記事のシェア画像が一斉に壊れる
- 表示ドメインから配信するほうがドメイン評価の観点でも自然で、外部ホスト依存も減らせる
実装は、ビルド前にアイキャッチを取得して1200×630のJPEGへ変換し、公開ディレクトリに書き出す処理を追加しました。ポイントは3つです。
- 差分方式にする(取得元URLが同じでファイルが存在すればスキップ)。再実行時は処理対象0件で終わる
- 画像生成が失敗してもビルドは通す。生成処理と本体ビルドを切り離す
- アイキャッチが無い記事は、サイト共通の既定画像へフォールバックさせる
実測値は、生成対象となった167本で平均69KB・合計+11.25MB。現在も本番の記事を叩くとhttps://graciauto.jp/og/1994.jpgのように表示ドメイン配下を指し、200 / image/jpeg / 約72KBが返ります。軽量化施策の成果を打ち消さない範囲に収まっています。
—
5. 第4段:キャッシュ。ただし「最後」に見る
1〜3が正しいことを確認してから、初めてキャッシュを疑います。
前提として押さえておきたいのは、反映のタイミングは経路ごとに違うという点です。
| 経路 | 反映の目安 |
|---|---|
| SNSのシェアカード | タグ修正は即時反映。ただし取得済みのURLはキャッシュが残ることがある |
| Google検索のサムネイル | 再クロール待ち。数日〜2週間程度 |
ここを混同すると「直したのに変わらない」という誤った結論になります。ECサイトのシェア画像を差し替えた案件では、SNSのカードは当日から新画像、Google検索のサムネイル置き換わりは再クロール待ちという説明を、作業完了の時点で先に伝えるようにしています。期待値を先に共有しておかないと、正しく直っているのに「効いていない」と判断されてしまうためです。
キャッシュへの対処は、プラットフォームによって手段の有無が分かれます。
- 再取得ツールが用意されているものは、それを叩けば更新できる
- 手段が公開されていない経路もある。この場合、URLにクエリ(
?v=2など)を付けると別URLとして扱われ、新規取得になる
ただしクエリ付与はシェアするURLそのものが変わる副作用があり、多用する前提の手段ではありません。
だからこそ結論はひとつで、公開前に正しくしておく。これが唯一の確実な対策です。
—
6. og:imageの出どころが「テンプレートの外」にあることもある
最後に、切り分けが空振りするパターンを1つ。
テンプレートのどこを探してもog:imageの記述が無いのに、意図しない画像が出ることがあります。ECプラットフォームやCMSでは、管理画面の「ブランド設定」「サイト設定」に登録した画像が、テンプレート指定が無い場合の既定値として使われるためです。
実案件では、検索結果に出るサムネイルが意図と違う画像になっており、調べると2年近く前に管理画面へ登録されたままの素材が出どころでした。コード側に該当する記述が1行も無いので、テンプレートを読むだけでは見つかりません。
判定は、curlで実際に出力されているog:imageのURLを取得し、そのパスから置き場所を逆算するだけです(テーマのassets配下か、管理画面のアップロード領域か)。
対処は、管理画面の設定を直すか、テンプレート側で明示的に上書きするかの2択です。管理画面のAPI権限が無い環境では後者になりますが、その際はトップページや設定の無いページだけを上書きし、商品ページ・記事ページは各ページ固有の画像を維持する条件分岐にします。全ページ一律で上書きすると、ページごとに出ていた画像まで潰してしまいます。
—
7. 公開前チェックリスト
そのまま使える形にまとめます。公開作業の手順書に貼っておくことを想定しています。
□ curl で og:image / og:title / og:url が出力されているか
□ 2ページ以上で確認し、ページごとに内容が違うか
□ og:image が https:// から始まる絶対URLか
□ その画像URLが 200 を返すか(本番公開の直後に再確認)
□ 画像ホストの robots.txt が Disallow: / になっていないか
□ og:image が JPEG / PNG か(一括WebP変換の巻き添えを受けていないか)
□ サイズは 1200×630 前後・ファイルサイズは数十KB〜
□ og:url が各ページ自身のURLを指しているか(トップ固定になっていないか)
最後の項目は見落とされやすい箇所です。共通のメタ出力処理が全ページに無条件で同じ値を出していると、og:urlが全記事でトップページを指すことがあります。記事ページでは記事自身のURLを出すよう、文脈別に出し分けます。
—
まとめ
シェア画像が出ない問題は、キャッシュから疑うと遠回りになります。
- 第1段:タグがHTMLに出ているか。ブラウザでなく
curlで、複数ページで確認する - 第2段:絶対URLで200が返るか。ルート相対パスは公開場所が変わると壊れる
- 第3段:画像ホストがクローラーに開放されているか。200でも取得できていないことがある。ヘッドレス構成で起きやすく、記事本数ぶんまとめて無効になる
- 第4段:キャッシュ。SNSカードは即時、検索サムネイルは再クロール待ちで数日〜2週間
最も費用対効果が高いのは、公開手順の中に「画像URLが200か」の確認を1行入れておくことです。シェア画像の不具合は壊れていても画面上にエラーが出ないため、誰も報告してくれません。仕組みで確認するしかない領域です。
graciautoではサイト制作・リニューアルの際に、この確認をチェックリストとして工程に組み込んでいます。自社サイトのシェア画像が正しく出ているか判断がつかない場合は、お気軽にご相談ください。