WordPressに自前でcanonicalを追加(カテゴリ、タグ、カスタム投稿タイプ他に対応)

シェアする:

この記事をおすすめしたい人

  • WordPressでcanonicalが出力されなくて困っている人
  • 特に理由はないけど自分でcanonicalを出力してみたい人
  • つまりオレ

WordPressをVer.5.9にアップデートしたらcanonicalが出力されなくなって困っています。

検索しても同様の症状が見られないので、僕だけの環境(All In One SEO PackがVer.3.7.1のまま)かもしれないですが、なんともならないので自前でcanonicalを出力することにしました。

ネットで調べて楽しようとしたんですが、

似たような中途半端なコードばっかりでね…。

お前ら全員、同じとこからパクってるやろと…。

というわけで、WordPressが生成する可能性のあるページを全て調べてそれぞれで挙動をチェックして、自前でcanonicalを出力するところまでやります。

調査報告の量が多いので、

結果だけ欲しい人は最後のまとめに飛んで下さい。

自前でcanonicalを設定するまでの流れ

内容をシンプルにしたいので、

  • 現在のページの正規化されたURLを取得
  • WordPressが生成する可能性のある全てのページで取得
  • header.php内でHEADタグ内にcanonicalを出力

という流れでやります。

ほんとはアクションフックでHEADタグ内に追加する方法もあるんですが、function.phpをいじろうがheader.phpをいじろうが手間は変わらないと思うのでシンプルにいきます。

アクションフックの方が知りたい人は「アクションフック wp_head」とかで検索すると方法が分かると思います。

トップページ(フロントページ)のURL取得

トップページは特に問題はなさそうです。


//get_permalink()
ttps://dev.ore-shika.com/

//REQUEST_URI
ttps://dev.ore-shika.com/

以降、出力URL部分の「https://dev.ore-shika.com」は除外してこの↓ような形にしますね。


//get_permalink()
/

//REQUEST_URI
/

この他にも「設定」「一般」の「サイトアドレス」を取得する方法(get_bloginfoとかhome_urlとか)もありますが、以降のコードと共通化するために「get_permalink()」を採用します。

トップページの判定には「is_front_page()」が使えるので、これ↓でOKです。


if ( is_front_page() ) {
  $url = get_permalink();
}

記事一覧ページのURL取得

ここは人によって設定の仕方が変わります。

というのも、ネットでいろいろ見ていると多くの人はトップページ(is_front_page)が記事一覧ページ(is_home)を兼ねているケースが多そうだからです。


//トップページ
/

//記事一覧ページ
/

対して僕はこの↓ように設定することが多いです。


//トップページ
/

//記事一覧ページ
/post/

どちらでも対応できるコードにしたいと思います。

とりあえずURLの取得

さっそく面倒なこと↓になりました。


//get_permalink()
/post/xxxxxxxx/

//REQUEST_URI
/post/

「get_permalink()」は現在アクティブな記事の情報を拾ってくるようで、「記事一覧ページ自体」ではなく、「記事一覧ページの最初に表示する記事」のURLを拾ってきてしまいます(xxxxxxxxは記事ID)。

「REQUEST_URI」を使った方法なら問題なさそうですが、2ページ目を開くとこう↓なります。


//get_permalink()
/post/yyyyyyyy/

//REQUEST_URI
/post/page/2/

「get_permalink」の方はもういいですね。 問題は「REQUEST_URI」の方。

はたして「/page/2/」が付くのは正規URLとして正しいのか。

そもそもcanonicalというのは「このページの正しいURL」を検索エンジンに伝えるもので、僕は「URLに余計なオプションが付いている場合にも元のURLを伝える」手段と理解しています。 あとはやむない重複ページとか。

この「/page/2/」はまさに「余計なオプション」なんじゃないでしょうか。

試しに僕が使ってるプラグインの2ページ目のcanonicalを見てみると、


//All In One SEO Pack(Ver.3.7.1)
/post/

//AMP
/post/page/2/

見事に分かれましたね。

「どっちが正しいか」ですが、canonicalが検索エンジンへ通知する以上、「記事一覧ページはここだぞ!」というのが記事一覧ページのcanonicalの意義じゃないのかなーと僕は考えています。

なので僕はそっちを採用しますが、そうじゃない人もいると思うので両対応のコードを考えます。

canonicalの設定

「トップページ=記事一覧ページ」のパターンと「記事一覧ページが別にある」パターン、2ページ目を「/post/」とするのか「/post/page/2/」とするのかで場合分けして考えます。

まずは記事一覧ページの1ページ目URLの取得

「get_permalink()」を使うと一覧内の最初の記事URLを、「REQUEST_URI」だと2ページ目以降のURLも拾ってきてしまうので、どのパターンでも正しくURLを拾う方法を考えます。

上でも書いた通り、「トップページ=記事一覧ページ」のパターンと、「記事一覧ページが別にある」パターンの2つがあります。

これは「設定」「表示設定」の「ホームページの表示」の項目で、「固定ページ」を選択肢「投稿ページ」を指定していると「記事一覧ページがトップページとは別にある」と判定されるようです。

これを取得する関数が「get_option()」で、引数に「page_for_posts」を指定してやると記事一覧ページに指定しているページIDを返してくれます。 そうでない場合は「0」が返るようです。

すなわち、条件分岐はこの↓ようになります。


if ( get_option('page_for_posts') ) {
  //記事一覧ページがトップページとは別にある
} else {
  //記事一覧ページ=トップページ
}

なので、1ページ目のURLを「$home_url」とするとこの↓ようになります。


$home_url = home_url().'/';

if ( get_option('page_for_posts') ) {
  $home_url = get_permalink(get_option('page_for_posts'));
}

僕のやり方はマイノリティっぽいので、デフォルトではトップページに指定、もし別に設定されていればURLを取得、という形です。

ちなみに。

「get_option()」はともかく、「page_for_posts」の部分を公式サイトから見つけてくるのはけっこう手間なんですが、Google Chromeで設定項目を右クリックして「検証」を選択すると、該当部分のコードが見えてIDやらNAMEやらが見えるので、推測するのはけっこう簡単です。

2ページ以降も同じURLにする場合

1ページ目のURLは上で無事に取得できたので、特にすることはありません。

記事一覧ページの判定は「is_home()」でできるので、この↓ようなコードでcanonicalが設定できます。


if ( is_home() ) {
  $canonical = home_url().'/';

  if ( get_option('page_for_posts') ) {
    $canonical = get_permalink(get_option('page_for_posts'));
  }
}

2ページ目以降もcanonicalに反映するパターン

「REQUEST_URI」の方はページ番号込みで取得できてますが、GET(「?num=5」みたいなの)も取得してしまうのと、そもそも2ページ目以降の判定が面倒なので、別の方法を考えます。

そこで登場するのがグローバル変数の「$paged」です。 「$paged」には2ページ目以降だとページ番号が入り、1ページ目では「0」が入ります。 「/page/1/」で1ページ目を開いた場合にも「0」でした。

なので「$paged」が設定されていた場合にURL末尾にページ番号を追加するコード↓です。


if ( is_home() ) {
  $canonical = home_url().'/';

  if ( get_option('page_for_posts') ) {
    $canonical = get_permalink(get_option('page_for_posts'));
  }
  
  if ( $paged > 0 ) {
    $canonical .= 'page/'.$paged.'/';
  }
}

「$paged > 0」の部分の評価は「$paged > 1」の方が正しい気もしますが、「/page/1/」でも問題なく開くのでより安全そうな「0」にしています。

「/page/」の部分がハードコードっぽくなってますが、WordPressの設定画面からここを変更する項目がないので通常の手段では変更不可っぽいのでこれで問題ないでしょう。

固定ページ・投稿ページのURL取得

固定ページ(page)・投稿ページ(post)のURLの生成のされ方は「設定」「パーマリンク」の設定によって変わります。 「/post/%postname%/」と指定していた場合のURL生成は、ページIDを「xxxxxxxx」「yyyyyyyy」として次のようになりました。


//固定ページ
//get_permalink()
/xxxxxxxx/
//REQUEST_URI
/xxxxxxxx/

//投稿ページ
//get_permalink()
/post/yyyyyyyy/
//REQUEST_URI
/post/yyyyyyyy/

上でも書きましたが「REQUEST_URI」の場合GETもURLとしてしまうので、ここでは共に「get_permalink()」を採用します。

固定ページ・投稿ページのcanonical指定

固定ページの判定には「is_page()」、投稿ページの判定には「is_single()」を使って、この↓ように。


if ( is_page() || is_single() ) {
  $canonical = get_permalink();
}

ページ分割されている場合

記事一覧ページと同様に、グローバル変数「$page」(記事一覧は$paged)にページ番号が入るようです。

1ページ目は「0」で、「/1/」指定の場合も「0」。

なのでURLはこの↓ようになります。


if ( is_page() || is_single() ) {
  $canonical = get_permalink();
  
  if ( $page > 0 ) {
    $canonical .= $page.'/';
  }
}

カスタム投稿タイプの場合

カスタム投稿タイプはis_singleが真を返して、get_permalinkでURLを取得できるので上のコードで問題なさそうです。

個別に処理したい場合はグローバル変数「$post_type」にカスタム投稿タイプのスラッグが入っているので、カスタム投稿タイプに合わせて処理することもできます。

カテゴリアーカイブページのURL取得

カテゴリにフィルタがかかっているだけで、位置付け的には記事一覧ページと同じです。

「get_permalink()」だと一覧内の最初の記事のURLを、「REQUEST_URI」だと概ね正確にURLを取得できます。 「REQUEST_URI」のデメリット解消のために、WordPress自体の機能でカテゴリアーカイブページのURLを取得します。


$url = get_category_link($cat);

グローバル変数「$cat」でカテゴリID(タグID)を取得して、そのIDから「get_category_link()」でカテゴリアーカイブページのURLを取得しています。

カテゴリアーカイブページのcanonical指定

カテゴリアーカイブページの判定は「is_category()」なので、コードはこの↓ようになります。


if ( is_category() ) {
  $canonical = get_category_link($cat);
}

2ページ目以降もcanonicalに反映する場合

「/cat/xxxx/page/2/」のcanonicalを「/cat/xxxx/」ではなく「/cat/xxxx/page/2/」にしたい場合です。

記事一覧ページと全く同じ挙動だったので、この↓ような感じでURLを取得できます。


if ( is_category() ) {
  $canonical = get_category_link($cat);
  
  if ( $paged > 0 ) {
    $canonical .= 'page/'.$paged.'/';
  }
}

タグアーカイブページのURL取得

カテゴリアーカイブページと似ていますがそれぞれの関数名・変数名が異なります。

タグアーカイブページのcanonical指定

タグアーカイブページの判定は「is_tag()」なので、コードはこの↓ようになります。


if ( is_tag() ) {
  $canonical = get_category_link($tag_id);
}

URLの取得に「get_category_link()」を使用していますが一般的には「get_tag_link()」が使われるようです。 リファレンスを見るとget_tag_linkは内部的にget_category_linkを使用しているそうで、後でコードを共通化するためにget_category_linkの方を使っています。

グローバル変数「$tag_id」にはタグのIDが入っています。 「$tag」というグローバル変数もあるんですが、こちらはタグのスラッグが入っていて、get_category_link・get_tag_link共に引数は「IDまたはオブジェクト」なのでスラッグのままでは使えません。

2ページ目以降もcanonicalに反映する場合

記事一覧ページと全く同じ挙動だったので、この↓ような感じでURLを取得できます。


if ( is_tag() ) {
  $canonical = get_category_link($tag_id);
  
  if ( $paged > 0 ) {
    $canonical .= 'page/'.$paged.'/';
  }
}

タクソノミーアーカイブページのURL取得

タクソノミーというのは「独自タグ」とでも言えばいいのでしょうか。 「カテゴリ」でも「タグ」でもない新たに追加するグループ分けのラベルです。

その性質上、カテゴリアーカイブページと似ていてそれぞれの関数名・変数名が異なります。

タクソノミーアーカイブページのcanonical指定

タクソノミーアーカイブページの判定は「is_tax()」なので、コードはこの↓ようになります。


if ( is_tax() ) {
  $canonical = get_term_link($term, $taxonomy);
}

グローバル変数「$term」にタクソノミーのスラッグが入っています。 URL取得用の「get_term_link()」はスラッグにも対応しているのでこのまま使えます。

グローバル変数「$taxonomy」はタクソノミーのIDです。 タクソノミーは「出身地」「年齢」のように複数追加することも可能なので、「どのタクソノミーなのか」をIDで指定します。

2ページ目以降もcanonicalに反映する場合

記事一覧ページと全く同じ挙動だったので、この↓ような感じでURLを取得できます。


if ( is_tax() ) {
  $canonical = get_term_link($term, $taxonomy);
  
  if ( $paged > 0 ) {
    $canonical .= 'page/'.$paged.'/';
  }
}

投稿者アーカイブページのURL取得

投稿者アーカイブページもカテゴリアーカイブページと似ていて、それぞれの関数名・変数名が異なります。

投稿者アーカイブページのcanonical指定

投稿者アーカイブページの判定は「is_author()」なので、コードはこの↓ようになります。


if ( is_author() ) {
  $canonical = get_author_posts_url($author);
}

グローバル変数「$author」には投稿者のIDが入っているので、「get_author_posts_url()」でURLを取得しています。

2ページ目以降もcanonicalに反映する場合

記事一覧ページと全く同じ挙動だったので、この↓ような感じでURLを取得できます。


if ( is_author() ) {
  $canonical = get_author_posts_url($author);
  
  if ( $paged > 0 ) {
    $canonical .= 'page/'.$paged.'/';
  }
}

日付アーカイブページのURL取得

日付アーカイブページもちょっと手間が増えますが、基本的には記事一覧ページと似たような流れです。

日付アーカイブページのcanonical指定

まず、日付アーカイブページの判定は「is_date()」です。

次に日付アーカイブページは、

  • 年を指定
  • 年と月を指定
  • 年と月と日を指定

の3タイプがあり、URL取得関数が変わります。

なのでコードはこの↓ようになります。


if ( is_date() ) {
  $canonical = get_year_link($year);
  
  if ( $monthnum > 0 ) {
    $canonical = get_month_link($year, $monthnum);
    
    if ( $day > 0 ) {
      $canonical = get_day_link($year, $monthnum, $day);
    }
  }
}

流れはこの↓ような感じ。

  1. 日付アーカイブページ(is_dateが真)ならグローバル変数「$year」から「get_year_link()」でURLを取得
  2. 月指定($monthnumが1以上)なら「get_month_link()」からURL取得
  3. 日指定($dayが1以上)なら「get_day_link()」からURL取得

月のグローバル変数は「$month」じゃなくて「$monthnum」のようです。 これが分からなくてグローバル変数を全部出力して調べました…。

2ページ目以降もcanonicalに反映する場合

記事一覧ページと全く同じ挙動だったので、この↓ような感じでURLを取得できます。


if ( is_date() ) {
  $canonical = get_year_link($year);
  
  if ( $monthnum > 0 ) {
    $canonical = get_month_link($year, $monthnum);
    
    if ( $day > 0 ) {
      $canonical = get_day_link($year, $monthnum, $day);
    }
  }
  
  if ( $paged > 0 ) {
    $canonical .= 'page/'.$paged.'/';
  }
}

カスタム投稿タイプアーカイブページ

カスタム投稿タイプというのは「固定ページ」「投稿」に続く第3の投稿タイプです。 もちろん第4、第5も追加できます。

性質上、記事一覧に投稿タイプでフィルタをかけるだけです。

カスタム投稿タイプアーカイブページのcanonical指定

カスタム投稿タイプアーカイブページの判定は「is_post_type_archive()」なので、コードはこの↓ようになります。


if ( is_post_type_archive() ) {
  $canonical = get_post_type_archive_link($post_type);
}

グローバル変数「$post_type」にはカスタム投稿タイプのスラッグが入っているので、「get_post_type_archive_link()」でURLを取得しています。

2ページ目以降もcanonicalに反映する場合

記事一覧ページと全く同じ挙動だったので、この↓ような感じでURLを取得できます。


if ( is_post_type_archive() ) {
  $canonical = get_post_type_archive_link($post_type);
  
  if ( $paged > 0 ) {
    $canonical .= 'page/'.$paged.'/';
  }
}

検索結果ページはcanonicalはどうするべきか

検索結果ページってどうすればいいんですかね。

ページの判定は「is_search()」でできますが、URLはトップページに「?s=検索ワード」になるんですよね。

これを正規化するとトップページになるので特に処理しなくていいんじゃないのかな…。

検索専用のページを用意している場合も、get_permalinkで取得できるだろうから特別な処理は必要なさそうです。

もしおかしなことになる場合はis_searchで分岐して処理を追加してください。

404のcanonicalはどうするべきか

ネットで調べていると404の場合は「/404/」みたいにcanonicalを指定してたりする例があるんですが、404って

  1. 「/non-exist/」にアクセス
  2. 「/non-exist/」が存在しない
  3. 404を返す

みたいな流れだと思うんですよね。

となるとURL的には「/non-exist/」だけど、そのURLは存在しないので検索エンジンにはこのURLを伝えたくないわけじゃないですか。

ということは、404の場合はcanonicalを設定しなくていいんじゃないのかなって。

一応設定もできるように条件分岐して最後にまとめます。

まとめ

ここまでをまとめると、WordPressのページには次の3種類があります。

  • 投稿ページ(固定ページ含む)
  • アーカイブページ
  • その他のページ

それぞれでコードを共有できる部分があるのでこの3タイプに分けてコードをまとめてみます。


//とりあえず現在のパーマリンクを取得
$canonical = get_permalink();

//アーカイブページの処理
//記事一覧ページはis_archiveが偽なのでis_homeでも評価
if ( is_home() || is_archive() ) {
  $canonical = home_url().'/';
  
  //記事一覧ページがトップページ以外に設定されている場合
  if ( is_home() && get_option('page_for_posts') ) {
    $canonical = get_permalink(get_option('page_for_posts'));
  }
  
  //カテゴリアーカイブの処理
  if ( is_category() ) {
    $canonical = get_category_link($cat);
  }
  
  //タグアーカイブの処理
  if ( is_tag() ) {
    $canonical = get_category_link($tag_id);
  }
  
  //タクソノミーアーカイブの処理
  if ( is_tax() ) {
    $canonical = get_term_link($term, $taxonomy);
  }
  
  //投稿者アーカイブの処理
  if ( is_author() ) {
    $canonical = get_author_posts_url($author);
  }
  
  //日付アーカイブの処理
  if ( is_date() ) {
    $canonical = get_year_link($year);
    
    if ( $monthnum > 0 ) {
      $canonical = get_month_link($year, $monthnum);
      
      if ( $day > 0 ) {
        $canonical = get_day_link($year, $monthnum, $day);
      }
    }
  }
  
  //カスタム投稿タイプアーカイブの処理
  if ( is_post_type_archive() ) {
    $canonical = get_post_type_archive_link($post_type);
  }
  
  //2ページ目以降もcanonicalに設定する場合の処理
  //if ( $paged > 0 ) {
  //  $canonical .= 'page/'.$paged.'/';
  //}

//固定ページと投稿ページの処理
} else if ( is_page() || is_single() ) {
  //URLはget_permalinkで取得済み
  
  //ページが分割されている場合の処理
  //$pageが未定義エラー出る場合の対処を追加
  if ( isset($page) && $page > 0 ) {
    $canonical .= $page.'/';
  }
} else if ( is_search() ) {
  //検索結果ページの処理
} else if ( is_404() ) {
  //404の場合の処理
}

//404でなければcanonicalを出力
//404でも出力したい場合はifを削除
if ( !is_404() ) {
  print '<link rel="canonical" href="'.$canonical.'">'."\n";
}

これをheader.php内のHEADタグ内に追加してください。

たぶんこれでWordPressが出力する全てのページに対応できてそうですが、他に見つかったら追加しておきます。

以上、WordPressからお届けしました!

シェアする: