ページネーションのSEO ( rel=”canonical” / “next” / “prev” )

2018年8月9日に加筆・修正をしました。

ブログ記事や商品などの一覧を複数のページに分けてページネーション(ページ送り)を設置することがありますが、この場合にrel=”canonical”/”next”/”prev”をマークアップすることで閲覧ユーザーに加えて検索エンジンにも「続きものページ」であることを伝えることができます。今回はWordpressで構築したサイトにSEO関連プラグインを使用せずに実装する一手間について。
ページネーションの例
まずは設定完了後の内容です。下記のようにタイトルとディスクリプション、前後のページURLをheadタグ内にマークアップされるように設定します。現在表示しているページは「rel=”canonical”」を、前のページがある場合は「rel=”prev”」、後のページは「rel=”next”」をlinkタグに付けます。canonicalのURLを先頭ページに設定する方法がありますが、rel=”next”/”prev”をマークアップする場合はページ同士の関係性を検索エンジンに正確に伝えられないので素直に現在表示しているページのURLを設定します。

例)ブログ一覧の2ページ目

<title>(ページ名) | ページ 2 | samplesite.com</title>
<meta name="description" content="ページ名(2ページ目)をご覧いただけます。説明。">
...
<link rel="canonical" href="http://www.studiobusstop.com/blog/page/2/">
<link rel="prev" href="http://www.studiobusstop.com/busstop/blog/">
<link rel="next" href="http://www.studiobusstop.com/busstop/blog/page/3/">

head内とfunction.phpにマークアップ用の記述をする

一部コードをfunction.phpに記述する方法に変更しました。

まずは概要からです。固定ページ内ループの設定コード(A)を<?php wp_head; ?>の前へ個別に記述することとします。

テンプレート

<head>
    <meta charset="utf-8">
    A.ループの設定 / WP_Query
    <?php wp_head(); ?>
</head>

また、function.phpに下記の用途のコードを記述します。

function.php

<?php
    B.add_theme_support( 'title-tag' )の有効化
    C.タイトルの設定
    D.ディスクリプションの設定
    E.rel = "canonical"/"next"/"prev"の出力設定
?>

使用する変数

用途別に変数を整理します。

グローバル変数

$page(整数)

「固定ページ」「投稿ページ」の現在のページ番号を格納。ページ分割を前提としない場合は使用しません。

$paged(整数)

「カテゴリーページ」「タグページ」「アーカイブページ」の現在のページ番号を格納。今回は「固定ページ」でサブループを設定するときも、この変数を上書きして使用することとします。

$wp_query

メインループ(URLリクエストに応じて変化)のデータ一式を格納

独自変数

$my_query

サブループの記事データを格納

$is_page

$wp_queryを上書きする場合に固定ページの判定を格納

$max_num_pages

ループの総ページ数を格納

$canonical_url

現在のページURLを格納

$nextpage

次のページ番号を格納

使用するプロパティ

$wp_query->max_num_pages

メインループの総ページ数

$my_query->max_num_pages

サブループの総ページ数、$my_queryは独自変数。

A. ループの設定 / WP_Query

サブループ

固定ページにサブループを表示する場合は下記のように記述することとします。

$pagedと$my_queryにデータを代入 / 例)サブループの投稿タイプが「post」の場合

<?php
    // 現在のページ番号を設定
    $paged = get_query_var('paged') ? get_query_var('paged') : 1;
    $args = array(
        'post_type' => array('post'),
        'paged' => $paged,
        'posts_per_page' => 12, /* 表示するページ数 */
        'order' => 'DESC' /* 並び順 */
    );
    $my_query = new WP_Query($args);
?>

body内でのループ後のリセット処理

<?php wp_reset_postdata(); ?>

メインループ

headとbody内ループの間に別のサブループがあり、サブループ設定用の変数「$my_query」を使い回す場合は、固定ページにメインループを表示する場合もありますのであわせて記載しておきます。$wp_queryを上書きした場合は$argsの内容に基づいてページの判定内容が変わるので、タイトルやcanonicalの設定用に判定を入れておきます。リセット処理の内容も変えます。

$wp_queryを上書き / 例)メインループの投稿タイプが「post」の場合

<?php
    // 固定ページの判定 / true
    $is_page = is_page();
    $args = array(
        'post_type' => array('post'),
        'paged' => $paged,
        'posts_per_page' => 12, /* 表示するページ数 */
        'order' => 'DESC' /* 並び順 */
    );
    $wp_query = new WP_Query($args);
    // ↓ $wp_queryを上書き後はfalseを返す。
    // var_dump(is_page());
?>

body内でのループ後のリセット処理

<?php wp_reset_query(); ?>

B.add_theme_support( ‘title-tag’ )の有効化

タイトルの出力は、WordPress 4.1以降から比較的ラクに設定できるようになってます。

function.php

<?php
// -----------------------------------------
// タイトルタグの出力
// -----------------------------------------
// add_theme_support( 'title-tag' ); を使用する
function setup_theme() {
    add_theme_support( 'title-tag' );
}
add_action( 'after_setup_theme', 'setup_theme' );
?>

合わせて出力のカスタマイズも済ませておきます。

function.php

<?php
// -----------------------------------------
// タイトルからキャッチフレーズを削除する
// -----------------------------------------
function remove_tagline($title) {
    if ( isset($title['tagline']) ) {
        unset( $title['tagline'] );
    }
    return $title;
}
add_filter( 'document_title_parts', 'remove_tagline' );
// -----------------------------------------
// セパレータを任意のものに変更する
// -----------------------------------------
function custom_title_separator($sep) {
    $sep = '|';
    return $sep;
}
add_filter( 'document_title_separator', 'custom_title_separator' );
?>

C.タイトルの基本設定

標準設定では、カテゴリやタクソノミーページでのタイトル表示から投稿タイプタイトルが抜けてしまうので合わせて設定します。「○ページ目」であることも表示されるようにして内容の重複を防ぎます。「$is_page = true;」の場合の処理も加えます。

function.php

<?php
// -----------------------------------------
// タイトルの書き換え
// -----------------------------------------
function my_pre_get_document_title ( $title ) {
    global $wp_query, $paged, $is_page;
    // プロジェクト独自設定 -------------------------------------------------------------
    // サイトタイトル
    $site_title = __( 'サイトのタイトル(英語)', 'テーマのテキストドメイン' );
    // ページ番号表示の設定(標準設定の体裁に合わせる場合)。
    // JP: [ | ページ $paged ]
    $page_num = $paged > 1 ? ' | '.__( 'Page', 'テーマのテキストドメイン' ).' '.$paged : '';
    // -------------------------------------------------------------------------------
    if( $is_page ){
        $page_title = esc_html(get_the_title()).$page_num.' | '.$site_title;
        return $page_title;
    }else{
        // タクソノミータームページの設定をする場合
        if ( is_tax() ) {
            // タームタイトル
            $term_title = single_term_title("", false);
            // カスタム投稿ラベル名
            $post_type_label = esc_html(get_post_type_object(get_post_type())->label );
            $tax_title = $term_title.' - '.$post_type_label.$page_num.' | '.$site_title;
            return $tax_title;
        }else{
            return $title;
        }
    }
}
add_filter( 'pre_get_document_title', 'my_pre_get_document_title', 10, 1 );
?>

D.ディスクリプションの設定

こちらも「○ページ目」であることも表示されるようにします。アーカイブやタクソノミータームページなどの設定も適宜行います。カテゴリ(ターム)の「説明」を含める場合はcategory_description()を使うと、その内容を表示できます。

function.php

<?php
// -----------------------------------------
// ディスクリプション
// -----------------------------------------
function echo_description() {
    global $post, $paged;
    
    // ページ番号表示の設定
    // JP: %sページ目
    $page_num = $paged > 1 ? '('.sprintf(__('Page %s', 'テーマのテキストドメイン'), $paged).')' : '';
    
    if( is_home() ){
        $description = get_bloginfo('description');
    }elseif( is_tax() ){
       // 「説明」 改行を処理しておく
       $rep_str = array("\r\n","\r","\n");
       $cat_description = str_replace( $rep_str, '', strip_tags( category_description() ) );
       // タームタイトル
       $term_title = '['.single_term_title("", false).']';
       // カスタム投稿ラベル名
       $post_type_label = esc_html(get_post_type_object(get_post_type())->label );
       if( $cat_description ){
          // JP: %s%s%sをご覧いただけます。%s
          $description = sprintf(__('You can see %s%s%s.%s', 'テーマのテキストドメイン'), $post_type_label, $term_title, $page_num, $cat_description);
       }else{
          // JP: %s%s%sをご覧いただけます。
          $description = sprintf(__('You can see %s%s%s.', 'テーマのテキストドメイン'), $post_type_label, $term_title, $page_num);
       }
    }elseif( is_archive() ){
       // カスタム投稿ラベル名取得
       $post_type_label = esc_html(get_post_type_object(get_post_type())->label );
       // %s%sをご覧いただけます。
       $description = sprintf(__('You can see %s%s.', 'テーマのテキストドメイン'), $post_type_label, $page_num);
    }
    if( isset($description) ){
       echo '<meta name="description" content="'.$description.'">';
    }
}
add_action('wp_head', 'echo_description', 1);
?>

展開後

// アーカイブ一覧1ページ目表示時
<title>カスタム投稿ラベル名 | samplesite.com</title>
<meta name="description" content="カスタム投稿ラベル名をご覧いただけます。”>
// アーカイブ一覧2ページ目表示時
<title>カスタム投稿ラベル名 | ページ 2 | samplesite.com</title>
<meta name="description" content="カスタム投稿ラベル名(2ページ目)をご覧いただけます。”>
// カテゴリ一覧1ページ目表示時
<title>タクソノミーターム名 - カスタム投稿ラベル名 | samplesite.com</title>
<meta name="description" content="カスタム投稿ラベル名[タクソノミーターム名]をご覧いただけます。タームの「説明」”>
// カテゴリ一覧2ページ目表示時
<title>タクソノミーターム名 - カスタム投稿ラベル名 | ページ 2 | samplesite.com</title>
<meta name="description" content="カスタム投稿ラベル名[タクソノミーターム名](2ページ目)をご覧いただけます。タームの「説明」”>

E.rel = “canonical”/”next”/”prev”の出力設定

「rel=”canonical/next”/”prev”」を出力するための記述をします。個別記事分割の対応は今回は除きます。表示判定に「is_singular()」を使用すると固定ページ内サブループの際に意図した動作をしないので今回は「is_single()」で。下記の設定を行います。

  • 2ページ以降を含めたcanonical用URLの設定。
  • 総ページ数の設定($max_num_pages への数値代入)。
  • メインループの場合は$pagedの初期値が0なため、1ページ目用に「1」を代入。
  • 条件に応じて出力。

function.php

<?php
// -----------------------------------------
// canonical, prev, next
// -----------------------------------------
function rel_canonical_prev_next(){
    global $wp_query, $my_query, $paged, $is_page;
    if ( is_home() ) {
        $canonical_url = esc_url(home_url());
    } elseif ( is_category() ) {
        $canonical_url = get_category_link(get_query_var('cat'));
    } elseif ( is_tax() ) {
        $taxonomy = get_query_var('taxonomy');
        $canonical_url = get_term_link(get_queried_object_id(), $taxonomy);
    } elseif ( is_archive() ) {
        $canonical_url = get_post_type_archive_link( get_post_type() );
    } elseif ( is_page() || is_single() ) {
        $canonical_url = get_permalink();
    } elseif( is_404() ) {
        $canonical_url =  esc_url(home_url()).'/404';
    } else {
        $canonical_url  = esc_url(home_url());
    }
    // 例外として上書き
    if($is_page) {
        $canonical_url = get_permalink();
    }
    // 2ページ以降
    if ( is_category() || is_tax() || is_archive() || is_page() || is_single() ) {
        if ( $paged >= 2) {
            $canonical_url = $canonical_url.'page/'.$paged.'/';
        }
    }
    // 個別記事を除いて設定(分割をしない前提)
    if( !is_single() ){
        // 総ページ数
        if ( !isset($max_num_pages) ){
            if( isset($my_query) ){
                $max_num_pages = $my_query->max_num_pages;
            } else {
                $max_num_pages = $wp_query->max_num_pages;
            }
        }
        // メインループの場合は、1ページ目を表示すると$pagedに「0」が代入されるので、「1」を上書きする。
        $paged = $paged == 0 ? 1 : $paged;
        // 次のページ
        $nextpage = intval($paged) + 1;
    }
    // 出力
    // canonical
    // 年別アーカイブページと検索ページを除いて設定
    if( !is_year() && !is_page('search') ) {
        echo '<link rel="canonical" href="'.$canonical_url.'">';
    }
    // トップページを除いて設定
    if( !is_home() ) {
        // prev
        if ( !is_single() && !is_year() && $paged > 1  ){
            echo '<link rel="prev" href="'.previous_posts( false ).'">';
        }
        // next
        if ( !is_single() && !is_year() && ( $nextpage <= $max_num_pages ) ){
            echo '<link rel="next" href="'.next_posts( $max_num_pages, false ).'">';
        }
    }
}
add_action('wp_head', 'rel_canonical_prev_next', 10);
?>

また、デフォルトでマークアップされる、rel=”canonical/next”/”prev”の設定を解除しておきます。

function.php

<?php
// デフォルト設定のrel="canonical"の自動挿入を停止する
remove_action('wp_head', 'rel_canonical');
// 個別記事へのrel="next"、rel="prev"の自動挿入を停止する
remove_action('wp_head', 'adjacent_posts_rel_link_wp_head', 10, 0 );
?>

期待される効果

  • 重複する内容のマークアップによる検索エンジンからのペナルティー回避。
  • 検索エンジンがクロールしやすくなる。
  • Google Analyticsでコンテンツグルーピングが可能になる。

さいごに

SEO関連のプラグインは便利ですが、意図していないことをされる場合があるので、今回はこのようなコード記述による方法を記事として残すことにしました。head内でサブループの設定をしていますが、body内でのループ設定後に「<link rel=”prev”〜>」と「<link rel=”next”〜>」タグをhead内に挿入する方法がわからなかったため、このようにしています。以上です。

参考