WordPress でカテゴリーアーカイブのURLに /category を入れたくない問題を力技で解決する

Web

今回解決させること

カテゴリーアーカイブのURLから /category を取り除く

  • WordPressデフォルトのカテゴリーアーカイブのURL形式は以下。
    • /notes/category/カテゴリースラッグ/
  • このサイトでは、個別記事のURLを以下に設定している。
    • /notes/カテゴリースラッグ/記事ID
  • なので、上記にあわせてカテゴリーアーカイブのURLも以下のようにしたい。
    • /notes/カテゴリースラッグ/
  • つまり、カテゴリーアーカイブのURLから /category の部分を削除したい。

/category なしURLにアクセスした際の表示不具合を防ぐ

問題の挙動
  • カテゴリーアーカイブのURL形式は
    • /notes/category/カテゴリースラッグ/
  • なので、以下のURLは「正しくないURL」のはず。
    • /notes/カテゴリースラッグ/
  • が、実際にアクセスすると404にはならず、カテゴリーアーカイブの1ページ目が表示されてしまう。
何が困るのか?
  • カテゴリーアーカイブの2ページ目以降の正しいURLは
    • /notes/category/カテゴリースラッグ/page/2
  • なので、ページネーションのリンク先として生成される以下のURLは「正しくないURL」
    • /notes/カテゴリースラッグ/page/2
  • 実際にアクセスすると、こちらは404が表示される。
つまり

カテゴリーアーカイブURLから /category を抜いたURLにアクセスした場合、「1ページ目は問題なく見れるのに、2ページ目以降には行けないページが見れてしまう」状態になっている。

パーマリンク設定の仕組み

ダッシュボード>管理画面>設定>パーマリンク設定には、オプションとして

  • カテゴリーベース
  • タグベース

の設定欄がある。

管理画面のスクリーンショット

なにに使うの?

/category または /tag の部分を、任意の名前に変えるためのオプション機能。

どういう動作をしているのか?

  • WordPressの中では、どのURLを叩いたら、どのページを出す、というリライトルールが設定されている。
  • 「パーマリンク設定」の画面で設定することは、イコール、リライトルールを上書きすること。

設定されているリライトルールを確認する方法

wp_optionsテーブルのスクリーンショット

  • リライトルールはデータベース上の options テーブルに格納されている。
    • データベース名の接頭辞を特に指定していなければ wp_options という名前のテーブル
    • option_name が rewrite_rules という行の、option_value に値が入っている
  • 直接DBを見てもいいが、配列が読みにくいので、get_option()関数を使って参照する。
  • index.php などテーマファイル内に以下を記述して出力させる。
<pre>
<?php print_r(get_option('rewrite_rules'));?>
</pre>
  • 以下のような配列が取得できる。
Array
(
    [sitemap(-+([a-zA-Z0-9_-]+))?\.xml$] => index.php?xml_sitemap=params=$matches[2]
    [sitemap(-+([a-zA-Z0-9_-]+))?\.xml\.gz$] => index.php?xml_sitemap=params=$matches[2];zip=true
    [sitemap(-+([a-zA-Z0-9_-]+))?\.html$] => index.php?xml_sitemap=params=$matches[2];html=true
〜略〜
    [(.+?)/page/?([0-9]{1,})/?$] => index.php?category_name=$matches[1]&paged=$matches[2]
    [(.+?)/comment-page-([0-9]{1,})/?$] => index.php?category_name=$matches[1]&cpage=$matches[2]
    [(.+?)/?$] => index.php?category_name=$matches[1]
)
  • 叩かれたURLが左側の正規表現にマッチすると、右側のURLの内容が出力される。
  • マッチングは上から順に処理される。

カテゴリーベースを書き換えるとどうなるのか?

  • カテゴリーベースを書き換えると、以下の5行の「category」の部分が書き換わる。

    [category/(.+?)/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?category_name=$matches[1]&feed=$matches[2]
    [category/(.+?)/(feed|rdf|rss|rss2|atom)/?$] => index.php?category_name=$matches[1]&feed=$matches[2]
    [category/(.+?)/embed/?$] => index.php?category_name=$matches[1]&embed=true
    [category/(.+?)/page/?([0-9]{1,})/?$] => index.php?category_name=$matches[1]&paged=$matches[2]
    [category/(.+?)/?$] => index.php?category_name=$matches[1]
  • たとえば、カテゴリーベースに「topics」を設定して保存すると、以下のようになる。

[topics/(.+?)/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?category_name=$matches[1]&feed=$matches[2]
[topics/(.+?)/(feed|rdf|rss|rss2|atom)/?$] => index.php?category_name=$matches[1]&feed=$matches[2]
[topics/(.+?)/embed/?$] => index.php?category_name=$matches[1]&embed=true
[topics/(.+?)/page/?([0-9]{1,})/?$] => index.php?category_name=$matches[1]&paged=$matches[2]
[topics/(.+?)/?$] => index.php?category_name=$matches[1]
  • つまり、対処法としてよく出てくる「カテゴリーベースに .(ドット) を入れる」という行為は、以下のリライトルールを定義しているということ。
[./(.+?)/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?category_name=$matches[1]&feed=$matches[2]
[./(.+?)/(feed|rdf|rss|rss2|atom)/?$] => index.php?category_name=$matches[1]&feed=$matches[2]
[./(.+?)/embed/?$] => index.php?category_name=$matches[1]&embed=true
[./(.+?)/page/?([0-9]{1,})/?$] => index.php?category_name=$matches[1]&paged=$matches[2]
[./(.+?)/?$] => index.php?category_name=$matches[1]
  • .(ドット)は正規表現上「何でもいい1文字」という恐ろしく便利で汎用的な文字。
    • これをやると、見た目上は /category を無くすことはできる。
    • が、上記にある page(ページネーション) や feed(RSSフィード) などの法則を確実にぶっ壊している。
    • しばしば遭遇する「ドットを入れたら○○のページが404になった」の原因は多分ここ。

/category/抜きURLの1ページ目だけが表示される原因

カテゴリーアーカイブURLから /category を抜いたURLにアクセスした場合、1ページ目が問題なく見れてしまう

という問題については、リライトルールの一番最後の行が原因。

[(.+?)/?$] => index.php?category_name=$matches[1]

WordPressサイト直下の、なにがしかの文字列で終わっているURLの場合、その文字列を category_name パラメータに渡して表示させる(=その文字列のカテゴリーアーカイブが表示される)という処理の結果だった。
このルールを適用させたくなければ、この行より手前にマッチする条件を追加する必要がある。

どうやって解決するか?

category というカテゴリーベースはそのままで、/category 抜きのURLを叩いたときに、出したいページが出る

を目指して、リライトルールとリダイレクトの設定を行う。

※あくまで「カテゴリーベースにドットを入れるよりはマシ」程度の力技です。
※複雑なことをしていないサイトなのでできる面もあり(後述の前提条件がないと成り立たない)。

ざっくり手順

  1. functions.php で add_rewrite_rule() 関数を使ってリライトルールを追加
  2. .htaccess でリダイレクトを設定

前提条件

  • カテゴリーの使い方として、以下を想定しない
    • 子カテゴリーを持つ
    • 1つの記事に複数カテゴリーを設定する
  • カテゴリーアーカイブのURLをWordPress側が自動で出力する関数を使わない
    • get_category_link()
    • wp_list_categories() など

目指すURL構造

ページ パス
フロントページ /notes/
個別記事 /notes/カテゴリースラッグ/記事ID
カテゴリーアーカイブ /notes/カテゴリースラッグ
カテゴリーアーカイブ(2ページ目) /notes/カテゴリースラッグ/page/2
カテゴリーアーカイブ(RSSフィード) /notes/カテゴリースラッグ/feed

1:リライトルールの設定

目指すもの

それぞれの正規表現マッチ条件のうち

category/(.+?)/

となっているところを

(存在するカテゴリースラッグいずれか)/

にしたい。

ソース

functions.phpに以下を追記する。

function custom_rewrite_basic() {
    //パイプ区切りのカテゴリーリスト作成
    $cats = get_categories( $args );
    $args = array(
        'hide_empty' => 0,
        'taxonomy' => 'category',
    );
    $catList = '';
    $i = 0;
    foreach ($cats as $cat) {
        if($i!=0){ $catList .= '|'; }
        $catList .= $cat->slug;
        $i++;
    }
    $catList = '('.$catList.')';
    //リライトルールを追加
    add_rewrite_rule($catList.'/feed/(feed|rdf|rss|rss2|atom)/?$', 'index.php?category_name=$matches[1]&feed=$matches[2]', 'top');
    add_rewrite_rule($catList.'/(feed|rdf|rss|rss2|atom)/?$', 'index.php?category_name=$matches[1]&feed=$matches[2]', 'top');
    add_rewrite_rule($catList.'/embed/?$', 'index.php?category_name=$matches[1]&embed=true', 'top');
    add_rewrite_rule($catList.'/page/?([0-9]{1,})/?$', 'index.php?category_name=$matches[1]&paged=$matches[2]', 'top');
    add_rewrite_rule($catList.'/?$', 'index.php?category_name=$matches[1]', 'top');
}
add_action('init', 'custom_rewrite_basic');

やっていることは以下。

  1. get_categories()関数で存在するカテゴリーすべてを取得し、カテゴリースラッグをパイプ区切りのリストに整形
  2. リライトルールから [category/(.+?)〜 で始まる行をコピーしてきて、add_rewrite_ruleの第1引数に左側を、第2引数に右側をセット。
    • 第3引数は top を指定(既存のリライトルールよりも優先的に適用させるため)
  3. 作成したパイプ区切りのリストを使って、第1引数のマッチ条件を書き換え。

【重要】リライトルールのキャッシュを更新する

  • functions.php にソースを反映したら、必ず、管理画面>設定>パーマリンク設定の[変更を保存]ボタンを押す。
    • WordPress内のリライトルールのキャッシュを更新する必要があるため。
    • これは flush_rules() 関数実行時と同じ動作にあたる。

追加されたリライトルール

[(adobe|google|mac|web|windows|event|life|music)/feed/(feed|rdf|rss|rss2|atom)/?$] => index.php?category_name=$matches[1]&feed=$matches[2]
[(adobe|google|mac|web|windows|event|life|music)/(feed|rdf|rss|rss2|atom)/?$] => index.php?category_name=$matches[1]&feed=$matches[2]
[(adobe|google|mac|web|windows|event|life|music)/embed/?$] => index.php?category_name=$matches[1]&embed=true
[(adobe|google|mac|web|windows|event|life|music)/page/?([0-9]{1,})/?$] => index.php?category_name=$matches[1]&paged=$matches[2]
[(adobe|google|mac|web|windows|event|life|music)/?$] => index.php?category_name=$matches[1]

たとえば web カテゴリーのアーカイブの場合は

  • /notes/web

というURLなので、リライトルールの最終行にたどり着く前に上記にマッチし、カテゴリーアーカイブページが正しく表示される。

  • /notes/web/page/2
  • /notes/web/feed

についても、上記で設定されているので、理想通りの挙動をするようになる。

参考リファレンス

2:リダイレクトの設定

目指すもの

  • /notes/category/カテゴリースラッグ(/なにがしか)

にアクセスされたら

  • /notes/カテゴリースラッグ(/なにがしか)

に飛ばす。

リダイレクトの目的

  • 今まで /category ありの状態で長期間運用していて、検索エンジンがそれを覚えている可能性があるので、URLが変わったことを301リダイレクトで示す
  • WordPressにカテゴリーアーカイブURLを生成する動きをさせた場合、/category が含まれたURLが生成されるので、もしそれが使用されていても /category なしURLに飛ぶようにしておく

ソース

WordPressサイト直下の .htaccess に以下を追記する。

RedirectMatch permanent ^/notes/category/([^/]+)/?(.+)? /notes/$1/$2

挙動

例:web というカテゴリースラッグのアーカイブページの場合

叩くURL リダイレクト先
/notes/category/web /notes/web に遷移
/notes/category/web/ /notes/web/ に遷移したあと /notes/web に遷移
/notes/category/web/page/2 /notes/web/page/2 に遷移
/notes/category/web/feed /notes/web/feed に遷移

おまけ

カテゴリーアーカイブページのURLを半静的に作る方法

前述の通り、

  • get_category_link()
  • wp_list_categories()

などを使ってしまうと、/category が含まれたURLが出力されてしまうので、代替方法を書いておく。

ループ外でカテゴリーリストを出力
  • get_categories()関数で取得する
  • 出力するカテゴリーを絞り込みたい場合は、上記リファレンスにあるパラメータを適宜 $args に設定する。
  • foreachの中でカテゴリースラッグを呼び出し、aタグのhref属性の中を好きに組み立てる。
<?php 
$cats = get_categories( $args );
$args = array(
    'taxonomy' => 'category',
);
if ( $cats ) {
echo '<ul>';
    foreach ($cats as $cat) {
        echo '<li><a href="/notes/'.$cat->slug.'">'.$cat->name.'</a> ('.$cat->count.')</li>';
    }
echo '</ul>';
}
?>
ループ内で記事に設定されているカテゴリーを出力
<?php
$cats = get_the_category();
if ( $cats ) {
    foreach( $cats as $cat ) {
        echo '<a href="/notes/'.$cat->slug.'">'.$cat->cat_name.'</a>';
    }
}
?>

参考エントリー

Comments

  • Name : ヤナーダ

    参考になりました! ありがとうございます!
    • Name : サトウ管理人

      コメントありがとうございます!お力になれたようで嬉しいです。

  • スパム対策のため、コメント本文にURLが含まれている場合は「承認待ち」となり、すぐに投稿が反映されません。ご了承ください。
  • 公序良俗に反する内容、個人が特定できる情報、スパム投稿と思われるコメント等については、予告なく編集・削除する場合があります。