WordPress でカテゴリーアーカイブのURLに /category を入れたくない問題を力技で解決する
今回解決させること
カテゴリーアーカイブの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を叩いたら、どのページを出す、というリライトルールが設定されている。
- 「パーマリンク設定」の画面で設定することは、イコール、リライトルールを上書きすること。
設定されているリライトルールを確認する方法
- リライトルールはデータベース上の 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を叩いたときに、出したいページが出る
を目指して、リライトルールとリダイレクトの設定を行う。
※あくまで「カテゴリーベースにドットを入れるよりはマシ」程度の力技です。
※複雑なことをしていないサイトなのでできる面もあり(後述の前提条件がないと成り立たない)。
ざっくり手順
- functions.php で add_rewrite_rule() 関数を使ってリライトルールを追加
- .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');
やっていることは以下。
- get_categories()関数で存在するカテゴリーすべてを取得し、カテゴリースラッグをパイプ区切りのリストに整形
- リライトルールから
[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
についても、上記で設定されているので、理想通りの挙動をするようになる。
参考リファレンス
- Rewrite API/add rewrite rule – WordPress Codex 日本語版
- Rewrite API/flush rules – WordPress Codex 日本語版
- 関数リファレンス/get categories – WordPress Codex 日本語版
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>';
}
?>
ループ内で記事に設定されているカテゴリーを出力
- ループ内なのでget_the_category()関数を使う。
<?php
$cats = get_the_category();
if ( $cats ) {
foreach( $cats as $cat ) {
echo '<a href="/notes/'.$cat->slug.'">'.$cat->cat_name.'</a>';
}
}
?>