SNS で共有してもタイトルと画像が出ない:OGP と twitter:card をテーマで出す

機能追加
広告WordPress・PHP を学べる本

Amazonのアソシエイトとして、WordPress functions.phpは適格販売により収入を得ています。

共有しても、カードが出ない

管理しているサイトで、記事を SNS で共有したときの見え方を見直していたときのことです。SNS は、ページの head にある OGP のタグ(og:title や og:image など)を読んで、タイトルと画像の入ったカードを作ります。ところがこのサイトの head を見ると、OGP のタグがどのページにも出ていませんでした。

調べてみると、SEO のプラグイン(All in One SEO)にもテーマにも OGP を出す設定はあるのですが、どちらも OFF になっていました。設定を ON にする方法もありますが、本番の設定はそのままにしておきたかったので、テーマの中に OGP を出す小さな機能を作り、プラグインやテーマの設定が ON になったときは、そちらに任せる形にしました。

出すタグと、値の取り方

決めたことは、タイトル・ディスクリプション・URL を、SEO のプラグインが出している値とそろえることです。別に作ると、検索結果と SNS のカードでディスクリプションが違う、URL がずれる、といったことが起きやすくなります。

タグ 値
og:title SEO のプラグインの title(<title> と同じ)
og:description SEO のプラグインの meta description と同じ
og:url SEO のプラグインの canonical と同じ
og:image アイキャッチ画像。なければサイト共通の画像(1200×630)
og:type 記事は article、トップや一覧は website
twitter:card summary_large_image(大きな画像のカード)

X 用には twitter:card だけを出し、タイトル・ディスクリプション・画像は og: の値を読んでもらう形にしています(X の公式の説明は、今回は確かめられていません)。共通の画像は、Facebook などでよく使われる 1200×630 の大きさで作りました。

作ったファイル

ファイルはテーマのフォルダの中の functions フォルダに置き、functions.php から require_once で読み込んでいます。実際のファイルから、カテゴリーの一覧の説明文など、このサイトの作りに合わせた部分を外して短くしたものが次のコードです。

/wp-content/themes/(テーマのフォルダ)/functions/ogp.php
<?php
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

define( 'MY_OGP_IMAGE', '/img/ogp-default.jpg' ); // 1200×630
define( 'MY_OGP_IMAGE_W', 1200 );
define( 'MY_OGP_IMAGE_H', 630 );

// テーマに OGP を出す設定があるなら、それが ON かを返す(テーマに合わせて書き換える)
function my_ogp_theme_outputs() {
    return false;
}

// All in One SEO のソーシャルの出力が ON か(array( og, twitter ))
function my_ogp_aioseo_social() {
    if ( ! function_exists( 'aioseo' ) ) {
        return array( false, false );
    }
    try {
        return array(
            (bool) aioseo()->options->social->facebook->general->enable,
            (bool) aioseo()->options->social->twitter->general->enable,
        );
    } catch ( \Throwable $e ) {
        return array( false, false );
    }
}

function my_ogp_clean( $text ) {
    $text = html_entity_decode( wp_strip_all_tags( (string) $text ), ENT_QUOTES, 'UTF-8' );
    return trim( preg_replace( '/\s+/u', ' ', $text ) );
}

// <title> と同じ
function my_ogp_title() {
    $title = function_exists( 'aioseo' ) ? (string) aioseo()->meta->title->getTitle() : '';
    return my_ogp_clean( '' !== $title ? $title : wp_get_document_title() );
}

// meta description と同じ
function my_ogp_description() {
    $desc = function_exists( 'aioseo' ) ? (string) aioseo()->meta->description->getDescription() : '';
    if ( '' === $desc ) {
        $desc = get_bloginfo( 'description' );
    }
    return my_ogp_clean( $desc );
}

// canonical と同じ
function my_ogp_url() {
    $url = function_exists( 'aioseo' ) ? (string) aioseo()->helpers->canonicalUrl() : '';
    if ( '' === $url && is_singular() ) {
        $url = (string) wp_get_canonical_url();
    }
    return $url;
}

// アイキャッチ、なければ共通の画像。array( URL, 幅, 高さ )
function my_ogp_image() {
    if ( is_singular() ) {
        $thumb_id = get_post_thumbnail_id( get_queried_object_id() );
        $image    = $thumb_id ? wp_get_attachment_image_src( $thumb_id, 'full' ) : false;
        if ( ! empty( $image[0] ) ) {
            return array( $image[0], (int) $image[1], (int) $image[2] );
        }
    }
    return array( get_template_directory_uri() . MY_OGP_IMAGE, MY_OGP_IMAGE_W, MY_OGP_IMAGE_H );
}

function my_ogp() {
    if ( is_404() || is_search() || is_feed() || my_ogp_theme_outputs() ) {
        return;
    }
    list( $aioseo_og, $aioseo_tw ) = my_ogp_aioseo_social();

    if ( ! $aioseo_og ) {
        $desc  = my_ogp_description();
        $url   = my_ogp_url();
        $image = my_ogp_image();
        $type  = ( is_singular() && ! is_front_page() ) ? 'article' : 'website';

        echo '<meta property="og:type" content="' . esc_attr( $type ) . '">' . "\n";
        echo '<meta property="og:site_name" content="' . esc_attr( get_bloginfo( 'name' ) ) . '">' . "\n";
        echo '<meta property="og:title" content="' . esc_attr( my_ogp_title() ) . '">' . "\n";
        if ( '' !== $desc ) {
            echo '<meta property="og:description" content="' . esc_attr( $desc ) . '">' . "\n";
        }
        if ( '' !== $url ) {
            echo '<meta property="og:url" content="' . esc_url( $url ) . '">' . "\n";
        }
        echo '<meta property="og:image" content="' . esc_url( $image[0] ) . '">' . "\n";
        echo '<meta property="og:image:width" content="' . (int) $image[1] . '">' . "\n";
        echo '<meta property="og:image:height" content="' . (int) $image[2] . '">' . "\n";
    }
    if ( ! $aioseo_tw ) {
        // タイトル・ディスクリプション・画像は og: の値を使ってもらう
        echo '<meta name="twitter:card" content="summary_large_image">' . "\n";
    }
}
add_action( 'wp_head', 'my_ogp', 5 );
書き方 役目
add_action( 'wp_head', 'my_ogp', 5 ) head の中に出す。優先度を小さくして、head の前のほうに出す
my_ogp_theme_outputs() テーマの OGP の設定が ON なら、何も出さない。テーマの設定の読み方に合わせて書き換える
my_ogp_aioseo_social() SEO のプラグインの Facebook(og:)と X(twitter:)の出力が ON かを見る。ON の分はここでは出さない
function_exists( 'aioseo' ) プラグインがないときや止めたときは、WordPress の関数の値を使う
try と catch ( \Throwable $e ) プラグインの設定の持ち方が変わってエラーになっても、ページを止めない
getTitle()・getDescription()・canonicalUrl() プラグインが <title>・meta description・canonical に出している値を、そのまま使う
my_ogp_clean() タグと文字参照を外し、空白をまとめる。esc_attr() で二重に文字参照にならないようにする
wp_get_attachment_image_src( …, 'full' ) アイキャッチの URL・幅・高さ
is_404() || is_search() || is_feed() 検索結果や見つからないページには出さない

SEO のプラグインの関数は、プラグインの中の作りに頼っているので、プラグインを更新したときは、OGP のタグが今までどおり出ているかを確かめるようにしています。

二重に出さないために

OGP のタグが同じページに 2 組あると、SNS がどちらを読むか分からなくなります。そのため、出す前に、SEO のプラグインのソーシャルの出力と、テーマの OGP の設定を見て、ON になっているほうに任せています。SEO のプラグインは、Facebook 用(og:)と X 用(twitter:)を別々に ON にできるので、この機能も、og: と twitter: を分けて判断しています。

なお、SEO のプラグインやテーマの設定を ON にしたときに、実際にこちらが出なくなるかは、本番の設定を変えないため、今回は試していません。

確かめた結果

本番から複製した作業環境で、記事 3 件とトップのページを HTTP で開き、head の中のタグを取り出して比べました。確かめた記事にはアイキャッチがないため、画像は共通の画像になっています。

確かめたこと 結果
出た og: のタグ 4 ページとも、type・site_name・locale・title・description・url・image・image:width・image:height
og:description と meta description 4 ページとも同じ
og:url と canonical 4 ページとも同じ
og:title と <title> 4 ページとも同じ
og:title・twitter:card の数 4 ページとも 1 つずつ(二重になっていない)
twitter:card summary_large_image
og:image 共通の画像(1200×630)

あわせて、この記事の例のコードを検証用スクリプトから読み込み、記事 1 件を開いた状態にして、実際のテーマの関数の出力と比べました。実際のファイルにだけある og:locale の 1 行を除いて、出たタグと値は同じでした。php -l もエラーなしです。

共有したときの見え方の確かめ方

Facebook には、URL を入れると OGP のタグの読み取り結果と、共有したときの見え方を表示する「シェアデバッガー」があります(Meta for Developers のツールで、使うには Facebook へのログインが要ります)。SNS は一度読んだページの情報をしばらく覚えているので、タグを直したあとは、このツールで読み直させてから確かめます。

X については、公式の確かめのツールが今も使えるかを今回確かめられなかったため、ここでは紹介していません。タグそのものは、ページのソースを開いて og: と twitter:card が 1 組ずつあるかを見るのが確実です。

気をつけること

  • SEO のプラグインやテーマの OGP の設定をあとで ON にしたら、ページのソースでタグが 1 組だけかを確かめる
  • 共通の画像は、テーマのフォルダに置いているので、テーマを更新するときに消さない
  • テーマに入れているので、テーマを切り替えると出なくなる
  • ページキャッシュを使っているサイトでは、入れたあとにキャッシュを消してから確かめる

まとめ

  • SNS でカードが出ないときは、まず head に OGP のタグがあるかを見る
  • og:title・og:description・og:url は、SEO のプラグインの title・meta description・canonical と同じ値を使うと、ずれない
  • 画像はアイキャッチ、なければ 1200×630 の共通の画像にする
  • SEO のプラグインやテーマの OGP の設定が ON のときは出さず、二重にしない
  • 見え方は、Facebook のシェアデバッガーやページのソースで確かめる

参考