All in One SEOからの卒業。19のmetaタグをプラグインを使わずにphpで置き換える
- 2022年7月1日 00:00
- サイト制作
- All in One SEO, All in One SEO Pack, metaタグ, PHP, WordPress
- 使用ツール:VS Code、WordPress5.8〜
「重い」「記事の更新がいつまで経っても終わらなくなる」などのよく聞く不自由は無かった(気付いてないだけであったのかもしれない)ですが、meta情報はそろそろAll in One SEOに頼らずやりたかったのでPHPを勉強&実践。最も大きな理由はAll in One SEO設定後に出てくる次のフレーズにイラァっとしたからです。うっさいわ!
書籍には「気にするな」って書いてあるんですけどね…
おめでとうございます、あなたのサイトはSEO対応ではありません!
問題は「記事のどこからこれらの情報を参照するか」「どのページごとに出し分けるか」です。
やったこと
このブログ記事は長いので、順番通りにやらなくても大丈夫。興味のあるところから始めていきましょう。
- og:title含むタイトルタグをphpで出し分けるように変更
- 固定ページ、投稿ページのdescriptionを[抜粋]からの取得に。無い場合は本文からの取得に
- カノニカルURLの表示
- 条件分岐の少ないメタ情報の出力
- OGP画像には原則アイキャッチ画像を使用。
- 無い場合は共通画像に。このサイトのアイキャッチは正方形なのでTwitter Cardではsumallyを、共通画像は1:1.919のものを使っているのでsummary_large_imageを出力するように設定
- また、カスタムフィールドにURLを入力すればその画像をOGP画像として出力するようにコーディング
- カスタムフィールドに検索避け用の値があればnoindex,nofollowのrobotsタグを出力
「あまり大きくいじらずにdescriptionやOGP画像を取得できるようにする」をコンセプトに、[抜粋]や[アイキャッチ]など、すでにある機能を活かしてメタ情報を出力。
というのも、All in One SEOを停止したあとの影響を最小限にしたかったためです。個別修正が発生すると辛いですし…
wp_head();
を使う場合はどうしてもfunctions.phpをいじる必要があり、ミスるとリカバリーするのが大変※なので、複数のテンプレートファイルを組み合わせて作るようにしています。
※この記事で紹介しているphpをwp_head();
と一緒に書くと、metaタグの重複出力になってしまうため外している、という理由もあります。All in One SEOが有効な場合もwp_head();
でmetaタグを出力するので、うっかりすると重複することも。
まだできていないこと
- 画像アップロードによるOGP画像設定。現状ではカスタムフィールドにURLを直接入力することで実現している。
- 検索避けの有無はカスタムフィールドでチェックボックスを使いたかった。チェックボックスを表示することまではなんとかなったものの、値の取得までたどりつけなかったので断念。
始める前に
まずは、All in One SEOが出力していたメタ情報、および当サイトのヘッダーのファイル構造を見てみましょう。
<meta name="description" content="ディスクリプション"/>
<meta name="robots" content="max-video-preview:-1"/>
<link rel="canonical" href="ページのURL" />
<meta property="og:site_name" content="サイト名" />
<meta property="og:type" content="article" />
<meta property="og:title" content="ページタイトル | サイト名" />
<meta property="og:description" content="ディスクリプション" />
<meta property="og:url" content="ページのURL" />
<meta property="fb:admins" content="facebookのappID" />
<meta property="og:image" content="OGP画像のURL" />
<meta property="article:published_time" content="公開日時" />
<meta property="article:modified_time" content="更新日時" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="ページタイトル | サイト名" />
<meta name="twitter:site" content="Twitterアカウント" />
<meta name="twitter:domain" content="サイトのドメイン" />
<meta name="twitter:description" content="ディスクリプション" />
<meta name="twitter:image" content="OGP画像のURL" />
上記に<title>
タグも加えて19のmetaタグをAll in one SEOに頼っていました(案外多いな…)。これらをすべて満たせるようにテーマをいじります。
header.phpのファイル構造
<!doctype HTML>
<html lang="ja">
<head>
<!--中略。CSSやビューポート、PWA設定など-->
<?php get_template_part( 'meta', 'title' ); ?>
<?php get_template_part( 'meta', 'canonical' ); ?>
<?php get_template_part( 'meta', 'other' ); ?>
<?php get_template_part( 'meta', 'description' ); ?>
<?php get_template_part( 'meta', 'ogp' ); ?>
<?php get_template_part( 'meta', 'robots' ); ?>
</head>
<body>
<!--以下略。ナビゲーションや検索フォームなど-->
自作テーマを使っています。コードのメンテナンスを(自分比で)やりやすくするため、<head>
から</head>
にテンプレートファイルを呼び出す構成。このうちSEOに関わるmetaタグに使っているのは下記の6つです。
- meta-title.php
- meta-description.php
- meta-canonical.php
- meta-ogp.php
- meta-other.php
- meta-robots.php
1.タイトルを取得、タグに反映
meta-title.phpで処理するタグ
<title></title>
- meta property=”
og:title
“ - meta name=”
twitter:title
“
19タグのうち3つを処理します。のこりは16。
<?php
///////////////////////
////タイトル出力用////
///////////////////////
function getTitle(){
global $page, $paged;
if (is_front_page() or is_home()) {//トップページ
$title = get_bloginfo('name');
}
elseif(is_page() or is_single()) {//固定ページ、投稿ページ
$title = the_title('',' | ',false).get_bloginfo('name');
}
elseif(is_category() or is_tag()){//カテゴリー、タグページ
$title = single_term_title('',false). ' | '.get_bloginfo('name');
}
elseif(is_archive()){//アーカイブページ
$title = the_title('',' | ',false).get_bloginfo('name');
}
elseif(is_404()){//404ページ
$title = '404 | '.get_bloginfo('name');
}//ここまでで一旦ページ種類ごとの条件分岐は終了
if($paged >= 2 || $page >= 2) { //各ページに2ページ目以降がある場合は「nページ」を追加
$title = $title. ' | ' . sprintf('%sページ',max($paged,$page));
}
echo '<title>'.$title.'</title>';
echo '<meta property="og:title" content="'.$title.'" />';
echo '<meta name="twitter:title" content="'.$title.'" />';
}//関数定義ここまで
getTitle();
?>
php宣言のあと、関数function getTitle()(変数$titleがページ種類により変わり、echoでhtmlを出力する)を定義。最後にgetTitle();を実行するコードを書きました。
2.descriptionを[抜粋]から取得
meta-description.phpで処理するタグ
- meta name=”description”
- meta property=”og:description”
- meta name=”twitter:description”
ここでも3つのタグを処理します。残り13。
<?php
/////////////////////////////////////
///////メタディスクリプション出力用///////
/////////////////////////////////////
function get_description() {
if ( is_front_page() or is_home() ) { //トップページはブログの説明文を表示
$description = get_bloginfo( 'description' );
} elseif ( is_category() ) { //カテゴリページはカテゴリの説明文を表示。説明文がなければカテゴリー名そのものを表示
$description = category_description();
if($description==''){
$description = single_cat_title('カテゴリー:「',false).'」のページ';
}else{
$description = category_description();
}
}
elseif ( is_tag() ) {
$description = tag_description();
if($description==''){
$description = single_cat_title('タグ:「',false).'」がついたページ';
}else{
$description = tag_description();
}
}
elseif ( is_page() or is_single() ) { //固定ページ、投稿ページ
$excerpt = get_the_excerpt();
if($excerpt==''){
$post = get_queried_object();
$description = strip_tags( $post->post_content );
$description = str_replace( ["\n","\r","<p>","</p>","<br />"], "", $description );
}else{
$description = str_replace( ["\n","\r","<p>","</p>","<br />"], "", $excerpt );
}
} else { //その他のページはブログ説明文を表示
$description = get_bloginfo( 'description' );
}
$description = mb_substr( $description, 0, 120 ); //文字数指定
echo '<meta name="description" content="'.$description.'"/>';
echo '<meta name="twitter:description" content="'.$description.'" />';
echo '<meta property="og:description" content="'.$description.'" />';
}
get_description();
?>
php宣言→関数定義→関数実行の流れはtitleと同様ですが、descriptionは条件分岐がtitleよりも複雑になっています。
- 共通文【
get_bloginfo( 'description' );
】 - ページ種類ごとの分岐【
if ( is_front_page() or is_home() )
】- カテゴリーやタグページの場合、そのカテゴリーやタグに説明があるか否かで分岐【
tag_description();、category_description();
】- 説明がある場合、その文章をdescriptionとして出力
- 説明がない場合、共通の文章を出力
- 固定、投稿ページの場合、「抜粋」に文章があるか否かで分岐【
get_the_excerpt();
】- 文章がある場合、抜粋の内容を出力
- 文章がない場合、記事の冒頭を出力【
$description = strip_tags( $post->post_content );
】
- カテゴリーやタグページの場合、そのカテゴリーやタグに説明があるか否かで分岐【
- 最後に文字数を調整して出力【
$description = mb_substr( $description, 0, 120 ); //文字数指定
】
tag_description()
などのありか
WordPressには先述のtag_description()
のように、サイトに入力している情報を取得するテンプレートタグがあります。これらは、どこに書いてある情報を参照しているのでしょうか?
get_bloginfo( 'description' );
[設定]タブの[一般]→[キャッチフレーズ]に入力されている内容が参照されます。
tag_description();、category_description();
[投稿]タブの[カテゴリー][タグ]からそれぞれのカテゴリー、タグの[編集]→[説明]に入力されている内容が参照されます。
3.各ページのURLを取得し、カノニカルタグを出力
meta-canonical.phpで処理するタグ
- link rel=”canonical”
- meta property=”og:url”
2つを処理します。残りは11。だんだん減ってきましたねフヒヒ
<?php
////////////////////////////
/////////canonical//////////
////////////////////////////
function getCanonical(){
global $page, $paged;
if (is_front_page() or is_home()) {//トップページ
$canonicalURL = get_home_url().'/';
}
elseif(is_page() or is_single()) {//固定ページ、投稿ページ
$canonicalURL = get_permalink();
}
elseif(is_category()){//カテゴリー
$category = get_the_category();
$categoryId = $category[0]->cat_ID;
$categoryURL = get_category_link($categoryId);
$canonicalURL = $categoryURL;
}
elseif(is_tag()){//タグ
$tagName = single_tag_title( '' ,false);
$tag = get_term_by('name', $tagName, 'post_tag');
$tagId = $tag->term_id;
$tagURL = get_tag_link($tagId);
$canonicalURL = $tagURL;
}
elseif(is_year()){//アーカイブページ(このサイトは年別のものしか用意していないのでis_yearのみ)
$year = get_the_time('Y');
$canonicalURL = get_year_link($year);
}
if($paged >= 2 || $page >= 2) { //2ページ目以降の場合
if (is_front_page() or is_home()){
$canonicalURL = $canonicalURL.'page/'.max( $paged, $page ).'/';
}else{
$canonicalURL = $canonicalURL.'/page/'.max( $paged, $page ).'/';
}
}
echo '<link rel="canonical" href="'.$canonicalURL.'">';
echo '<meta property="og:url" content="'.$canonicalURL.'">';
}
getCanonical();
?>
こちらもtitle、description同様の流れ。変数$canonicalURLを、ページの種類によって変えていき、htmlで出力するというものです。
複数ページに分かれる項目もあるため、冒頭のglobal $page, $paged;
でページ数を取得できるようにしておきます。
- トップページのURL【
if (is_front_page() or is_home())、get_home_url().'/';
】 - 固定ページ、投稿ページのURL【
elseif(is_page() or is_single())、get_permalink();
】 - カテゴリーページ
- タグページ
- タグに関しては、おまかせにしてしまうとurlとタグ名がズレてしまうことがある
- まずは変数
$tag
にget_term_by('name', $tagName, 'post_tag');
で現在のタグ名を取得 - 続いて変数
$tag_ID
で先程の$tag
に入れたタグ名のIDを取得。 - 変数
$tagURL
に、get_tag_link($tag_ID)
で当該IDのタグページのURLを取得したものを代入し、URLとタグ名がズレないようにする
- アーカイブページのURL
- 複数ページある場合はそれ用の文字列を追加
4.条件分岐がほぼないメタタグを出力
減ってきたとはいえまだ処理すべきタグが11も残っています。ここで一気に片付けてしまいましょう。
meta-other.phpで処理するタグ
- meta property=”og:type”
- meta property=”og:site_name”
- meta property=”fb:app_id”またはmeta property=”fb:admins”
- meta name=”twitter:site”
- meta name=”twitter:domain”
- meta property=”article:published_time”
- meta property=”article:modified_time”
一気に7つも減る!! 残り4つで、大詰めが近づいてきました。
<?php
/////////////////////////////////
////////共通メタ情報//////////////
/////////////////////////////////
if(is_home() or is_front_page()){
echo '<meta property="og:type" content="website" />';
}else{
echo '<meta property="og:type" content="article" />';
}
?>
<meta property="og:site_name" content="<?php bloginfo('name');?>" />
<meta property="fb:app_id" content="FacebookのApp ID" /><meta property="og:locale" content="ja_JP" />
<meta name="twitter:site" content="@Twitterアカウント" />
<meta name="twitter:creator" content="@Twitterアカウント" />
<meta name="twitter:domain" content="サイトのドメイン" />
<meta property="article:published_time" content="<?php the_time('Y/m/d'); ?> <?php the_time('G:i'); ?>" />
<meta property="article:modified_time" content="<?php the_modified_date('Y/m/d'); ?> <?php the_modified_time(); ?>" />
ここでは、それぞれに適切な値を入れていけばいいので簡単です。分岐は、og:typeがトップページの場合はwebsite、それ以外のページのときはarticleを出力する程度です。
php宣言がされている中でこれらのタグを全部echoするのは面倒なので、分岐部分まででphpを一旦区切り、他の部分はhtml内にphpを書き込む構造にしています。
5.皆さんお待ちかね(Gガンダム風に)OGP画像の設定
meta-ogp.phpで処理するタグ
- meta name=”twitter:image”
- meta property=”og:image”
- meta name=”twitter:card”
今回の3つを完了すれば残りはrobotタグ1種類。あとひといきです。
<?php
global $post;
//便宜上同じファイル名のように書いていますが、実際はdefaultOGP、tagOGP、catOGP、limitedOGPで定義する画像名は全部異なるものを指定してください。
$defaultDLC = get_template_directory_uri();
$defaultOGP = $defaultDLC.'/画像の置いてあるフォルダ/画像名'.'.拡張子';
$ogpField= get_post_meta($post->ID, 'OGP', true);
$ogpLimitedField= get_post_meta($post->ID, 'サイト限定', true);
$tagOGP = $defaultDLC.'/画像の置いてあるフォルダ/画像名'.'.拡張子';
$catOGP = $defaultDLC.'/画像の置いてあるフォルダ/画像名'.'.拡張子';
$limitedOGP = $defaultDLC.'/画像の置いてあるフォルダ/画像名'.'.拡張子';
if ( is_home() ) { //トップページは共通OGPを表示
echo '<meta name="twitter:image" content="'.$defaultOGP.'" />';
echo '<meta property="og:image" content="'.$defaultOGP.'" />';
echo '<meta name="twitter:card" content="summary_large_image" />';
} elseif ( is_category() ) {
echo '<meta name="twitter:image" content="'.$catOGP.'" />';
echo '<meta property="og:image" content="'.$catOGP.'" />';
echo '<meta name="twitter:card" content="summary_large_image" />';
}
elseif ( is_tag() ) {
if( is_tag('limited') ){
$tagOGP=$limitedOGP;
}
echo '<meta name="twitter:image" content="'.$tagOGP.'" />';
echo '<meta property="og:image" content="'.$tagOGP.'" />';
echo '<meta name="twitter:card" content="summary_large_image" />';
}
elseif ( is_single() or is_page() ) { //投稿・固定ページはアイキャッチで対応。なければ共通のものを
if(!empty($ogpField)){
$ogpURL = post_custom('OGP');
echo '<meta name="twitter:image" content="'.$ogpURL.'" />';
echo '<meta property="og:image" content="'.$ogpURL.'" />';
echo '<meta name="twitter:card" content="summary_large_image" />';
}
elseif(!empty($ogpLimitedField)){
echo '<meta name="twitter:image" content="'.$limitedOGP.'" />';
echo '<meta property="og:image" content="'.$limitedOGP.'" />';
echo '<meta name="twitter:card" content="summary_large_image" />';
}
elseif(has_post_thumbnail()){
$thumbnailURL = get_the_post_thumbnail_url(get_the_ID(), 'large');
echo '<meta name="twitter:image" content="'.$thumbnailURL.'" />';
echo '<meta property="og:image" content="'.$thumbnailURL.'" />';
echo '<meta name="twitter:card" content="summary" />';
}else{
echo '<meta name="twitter:image" content="'.$defaultOGP.'" />';
echo '<meta property="og:image" content="'.$defaultOGP.'" />';
echo '<meta name="twitter:card" content="summary_large_image" />';
}
}
elseif ( is_404() ) { //404はシェアされることはないのでOGPは出さない
}
else { //その他のページは共通画像で対応
echo '<meta name="twitter:image" content="'.$defaultOGP.'" />';
echo '<meta property="og:image" content="'.$defaultOGP.'" />';
echo '<meta name="twitter:card" content="summary_large_image" />';
}
?>
OGP画像を出し分けるphpはこれまでと比べると結構複雑です。コードの処理手順は下記の通り。
- 変数$postをglobalにして投稿全部を取得
- 変数$defaultDLCにget_template_directory_uri();を代入しテーマディレクトリのURLを取得
- 変数$defaultOGP、$tagOGP、$catOGP、$limitedOGPにそれぞれテーマディレクトリ直下の画像フォルダ、ファイル名を指定
- 変数$ogpFieldにget_post_meta($post->ID, ‘OGP’, true); で取得した各投稿のカスタムフィールド「OGP」に入力されている値を代入
- 4.と同様、変数$ogpLimitedFieldに get_post_meta($post->ID, ‘サイト限定’, true);で取得した各投稿のカスタムフィールド「サイト限定」に入力されている値を代入
- 条件分岐する
- トップページは共通画像、Twitterカードの出力はsummary_large_image
- ページ種類ごと(タグやカテゴリーページがシェアされることは少ないので、これはやらなくてもいいかも)
- [重要]個別投稿、固定ページ
- まず、カスタムフィールド「OGP」に入力されているかどうか見る
- 入力されている場合、その文字列を$ogpURLに代入し画像のURLとして使用。Twitterカードの大きさはsummary_large_image
- カスタムフィールド「OGP」に何も入力されていない場合は、カスタムフィールド「サイト限定」を見る
- 入力されている場合、$limitedOGPをOGPに使用。Twitterカードの大きさはsummary_large_image
- 「OGP」「サイト限定」いずれも設定されていない場合は、各投稿にアイキャッチがあるかどうかを見る。アイキャッチは正方形なので、Twitterカードの大きさはsummaryにして、シェアされたときも正方形に見えるように
- 「OGP」「サイト限定」が空欄、アイキャッチもない場合は、サイトで共通の画像を表示
- まず、カスタムフィールド「OGP」に入力されているかどうか見る
- 404ページは、シェアされないので何も出さない
- 他のページは共通画像でOGPを出す
「設定した画像によってsummaryとsummary_large_imageを分ける」という実装にできたのが個人的には大変ツボです。
カスタムフィールドの取得
投稿や固定ページ編集画面の下部にある[カスタムフィールド]から[名前]を入力し※、値を入力します。
※一度入力すればプルダウンから選択できるようになります。
例えば、$ogpField= get_post_meta($post->ID, 'OGP', true);
はカスタムフィールドの名前が「OGP」の「値」に入力された内容を参照するphpです。
6.robotsタグの出力
meta-robots.phpで処理するタグ
- meta name=”robots”
これが最後の19個目。お疲れ様でしたー!
<?php
////////////////////////////
/////////robots/////////////
////////////////////////////
$robots = '<meta name="robots" content="max-snippet:-1, max-image-preview:large, max-video-preview:-1"/>';
$robots_avoid = '<meta name="robots" content="noindex, nofollow, noarchive, nosnippet, noimageindex, max-video-preview:-1"/>';
if ( is_home() or is_front_page() ) {
echo $robots;
} elseif ( is_category() ) { //カテゴリページはインデックスしてもらう
echo $robots;
}
elseif ( is_tag() ) { //タグページは検索避け
echo $robots_avoid;
}
elseif ( is_single() or is_page() ) { //投稿・固定ページはどちらかに
$search_avoid = get_post_meta($post->ID, '検索避け', false);
if(empty($search_avoid)){
echo $robots;
}else{
echo $robots_avoid;
}
}
elseif ( is_404() ) { //404はインデックスの必要はないので検索避け
echo $robots_avoid;
}
else {
echo $robots;
}
?>
robotsタグに関しては、変数$robotsには検索エンジンにインデックスさせるためのコードを、変数$robots_avoidには検索避け用のコードを代入しておき、それをページ種類ごとに出し分ける、という処理になっています。
- 変数
$robots
に<meta name="robots" content="max-snippet:-1, max-image-preview:large, max-video-preview:-1"/>
を入れておく - 変数
$robots_avoid
には<meta name="robots" content="noindex, nofollow, noarchive, nosnippet, noimageindex, max-video-preview:-1"/>
を入れておく - ページ種類ごとに条件分岐する
- トップページ
- カテゴリーページ
- タグページ
- 個別投稿、固定ページ
- カスタムフィールド「検索避け」が空欄、あるいはカスタムフィールドがない場合、変数
$robots
の内容を出力 - カスタムフィールド「検索避け」に入力されている場合、変数
$robots_avoid
の内容を出力
- カスタムフィールド「検索避け」が空欄、あるいはカスタムフィールドがない場合、変数
- 404ページはインデックスさせない(そもそも404ならrobotsタグ自体いらないかも)
- 上記のいずれでもない場合は変数
$robots
の内容を出力
これにて、All in One SEOで出力していたmetaタグの置き換えは完了です。2021年9月には実装できていたのに、処理のフローを文章にするのにずいぶんとかかってしまいました。幸い、WordPressを6.0に更新しても無事に動いているコードです。
実装における基本思想は「すでに入力されている内容を参照して再利用」「ページ種類や、投稿の内容に応じて出しわける」。
利点は以下の2点です。
- 入力されている部分を使うので、サイトの構造を大きく変えなくていい
- 条件分岐で「何も入っていない場合は共通のものを出力」のコードを書いておくことで、あとから本来のものを入れるゆとりができる(最悪入れなくてもシェアされたときのサイトの体裁は保てる)
ただ、WordPressを使ってのサイト運営は、下記の条件と戦い続けなければいけません。
- WordPressのバージョン
- PHPのバージョン
- プラグインのバージョン
- 使っているプラグインの開発が続いているか
「●●ならOK」という手段はすぐになくなってしまうので、何かあったら自力で実装できるいろいろを身に着けておきたいところです。
参考記事
https://note.com/fkr_prss_tech/n/n25f6dfa413d8
テンプレートタグ – WordPress Codex 日本語版