2026年5月20日

2026年5月20日

WordPressでカスタムリライトルールを設定する方法【パーマリンク最適化】

はじめに

WordPressのリライトシステムは、ユーザーフレンドリーなURLを内部のクエリパラメータにマッピングする仕組みです。カスタムリライトルールを追加することで、/products/category/electronics/のような独自のURL構造を実現できます。本記事では、add_rewrite_rule()add_rewrite_tag()の使い方から、デバッグ方法まで詳しく解説します。

症状・原因

  • 独自のURL構造(例:/team/yamada//events/2026/05/)でカスタムコンテンツを表示したい
  • プラグインで追加したカスタムエンドポイントが404エラーになる
  • パーマリンク設定を変更しても新しいURLパターンが反映されない

解決手順

ステップ1:add_rewrite_rule()の構文と基本的な使い方

<?php
/**
 * カスタムリライトルールの基本構文
 * add_rewrite_rule( regex, redirect, priority )
 *
 * regex:    マッチさせるURLの正規表現(サイトのルートからの相対パス)
 * redirect: マッチした場合のリダイレクト先(内部クエリ文字列)
 * priority: 'top'(WordPress標準より前)または 'bottom'(後)
 */
add_action( 'init', 'my_add_rewrite_rules' );

function my_add_rewrite_rules() {

    // 例1:/team/{slug}/ → メンバープロフィールページ
    // 正規表現のキャプチャグループ $matches[1] が redirect に使われる
    add_rewrite_rule(
        '^team/([a-z0-9-]+)/?$',                    // 正規表現
        'index.php?pagename=team&member=$matches[1]', // リダイレクト
        'top'                                        // 優先度
    );

    // 例2:/events/{year}/{month}/ → 年月アーカイブ
    add_rewrite_rule(
        '^events/([0-9]{4})/([0-9]{2})/?$',
        'index.php?post_type=event&event_year=$matches[1]&event_month=$matches[2]',
        'top'
    );

    // 例3:/api/v1/{endpoint}/ → カスタムAPIエンドポイント
    add_rewrite_rule(
        '^api/v1/([a-z_-]+)/?$',
        'index.php?my_api_endpoint=$matches[1]',
        'top'
    );

    // 例4:末尾スラッシュあり・なし両方に対応する場合
    add_rewrite_rule(
        '^products/([a-z0-9-]+)/reviews/?$',
        'index.php?post_type=product&name=$matches[1]&product_tab=reviews',
        'top'
    );
}

ステップ2:add_rewrite_tag()でカスタムクエリ変数を登録する

<?php
/**
 * カスタムクエリ変数の登録
 * add_rewrite_tag() で WordPress に認識させる
 *
 * 引数: add_rewrite_tag( tag, regex, query )
 * tag:   %変数名% 形式のタグ名
 * regex: このタグが取りうる値の正規表現(バリデーション用)
 * query: 対応するクエリ文字列パラメータ
 */
add_action( 'init', 'my_add_rewrite_tags' );

function my_add_rewrite_tags() {
    // メンバースラッグ用のタグ
    add_rewrite_tag( '%member%', '([a-z0-9-]+)' );

    // イベント年月用のタグ
    add_rewrite_tag( '%event_year%',  '([0-9]{4})' );
    add_rewrite_tag( '%event_month%', '([0-9]{2})' );

    // カスタムAPIエンドポイント用のタグ
    add_rewrite_tag( '%my_api_endpoint%', '([a-z_-]+)' );

    // 商品タブ用のタグ(固定値のみ受け付ける)
    add_rewrite_tag( '%product_tab%', '(reviews|specs|qa)' );
}

// add_rewrite_tag() の代わりに query_vars フィルターを使う方法(後述ステップ3)
// add_rewrite_tag() はパーマリンク構造にも使えるが、
// 単純にクエリ変数を認識させるだけなら query_vars フィルターで十分

ステップ3:query_varsフィルターでカスタム変数をホワイトリスト登録する

<?php
/**
 * カスタムクエリ変数のホワイトリスト登録
 * WordPressは認識していないクエリ変数を無視するため、
 * query_vars フィルターで明示的に許可する必要がある
 */
add_filter( 'query_vars', 'my_register_query_vars' );

function my_register_query_vars( $vars ) {
    // 許可するカスタムクエリ変数を配列に追加
    $vars[] = 'member';
    $vars[] = 'event_year';
    $vars[] = 'event_month';
    $vars[] = 'my_api_endpoint';
    $vars[] = 'product_tab';

    return $vars;
}

/**
 * 登録したクエリ変数の値を取得する
 * get_query_var() で取得できる
 */
function my_get_custom_query_var( $var, $default = '' ) {
    $value = get_query_var( $var, $default );

    // 値のバリデーション(必要に応じて)
    if ( 'event_year' === $var && $value ) {
        $year = absint( $value );
        return ( $year >= 2000 && $year <= 2100 ) ? $year : $default;
    }

    return $value;
}

// テンプレート内での使用例
// $member_slug = get_query_var( 'member', '' );
// $event_year  = get_query_var( 'event_year', date('Y') );

ステップ4:template_redirectでカスタムURLのテンプレートを処理する

<?php
/**
 * カスタムURLにマッチした場合のテンプレート処理
 * template_redirect フックを使う
 */
add_action( 'template_redirect', 'my_handle_custom_endpoints' );

function my_handle_custom_endpoints() {

    // === メンバープロフィールページの処理 ===
    $member = get_query_var( 'member', '' );
    if ( $member ) {
        // メンバーデータを取得(カスタムポストタイプやデータベースから)
        $member_post = get_page_by_path( $member, OBJECT, 'team_member' );

        if ( ! $member_post ) {
            // メンバーが見つからない場合は404
            global $wp_query;
            $wp_query->set_404();
            status_header( 404 );
            nocache_headers();
            include get_query_template( '404' );
            exit;
        }

        // カスタムテンプレートを読み込む
        $template = locate_template( array(
            "team-member-{$member}.php",
            'team-member.php',
            'page.php',
            'index.php',
        ) );
        include $template;
        exit;
    }

    // === カスタムAPIエンドポイントの処理 ===
    $endpoint = get_query_var( 'my_api_endpoint', '' );
    if ( $endpoint ) {
        // APIレスポンスを返す
        header( 'Content-Type: application/json; charset=utf-8' );
        header( 'X-Content-Type-Options: nosniff' );

        $allowed_endpoints = array( 'posts', 'categories', 'tags' );
        if ( ! in_array( $endpoint, $allowed_endpoints, true ) ) {
            status_header( 404 );
            echo wp_json_encode( array( 'error' => 'Endpoint not found' ) );
            exit;
        }

        $data = my_get_api_data( $endpoint );
        echo wp_json_encode( $data );
        exit;
    }
}

/**
 * カスタムAPIデータを返す
 */
function my_get_api_data( $endpoint ) {
    switch ( $endpoint ) {
        case 'posts':
            $posts = get_posts( array( 'numberposts' => 10 ) );
            return array_map( function( $post ) {
                return array(
                    'id'    => $post->ID,
                    'title' => get_the_title( $post ),
                    'url'   => get_permalink( $post ),
                );
            }, $posts );
        default:
            return array();
    }
}

ステップ5:flush_rewrite_rulesのタイミングとデバッグ

<?php
/**
 * flush_rewrite_rules() のタイミング
 * リライトルールを追加・変更した後は必ずフラッシュが必要
 * ただし毎回のリクエストで実行するとパフォーマンスが大幅に低下する
 */

// 正しい実装:プラグインの有効化・無効化時のみフラッシュする
register_activation_hook( __FILE__, 'my_plugin_activate' );
register_deactivation_hook( __FILE__, 'my_plugin_deactivate' );

function my_plugin_activate() {
    // まずリライトルールを登録
    my_add_rewrite_rules();
    my_add_rewrite_tags();

    // 登録後にフラッシュ(有効化時のみ実行される)
    flush_rewrite_rules();
}

function my_plugin_deactivate() {
    // 無効化時にもフラッシュ(削除したルールを反映)
    flush_rewrite_rules();
}

// 開発中にリライトルールをデバッグする方法
function my_debug_rewrite_rules() {
    if ( ! current_user_can( 'manage_options' ) || ! isset( $_GET['debug_rewrite'] ) ) {
        return;
    }

    global $wp_rewrite;

    // 現在登録されているすべてのリライトルールを確認
    $rules = $wp_rewrite->rules;
    echo '<pre>';
    echo "=== 登録済みリライトルール ===\n";
    foreach ( (array) $rules as $regex => $redirect ) {
        if ( strpos( $redirect, 'my_' ) !== false || strpos( $regex, 'team' ) !== false ) {
            echo esc_html( $regex ) . "\n  → " . esc_html( $redirect ) . "\n\n";
        }
    }
    echo '</pre>';
    exit;
}
add_action( 'init', 'my_debug_rewrite_rules' );

// パーマリンク設定画面で「変更を保存」すると flush_rewrite_rules() が実行される
// 開発中は管理画面の「設定 → パーマリンク」を保存して手動でフラッシュ可能

/**
 * バージョン変更時に自動的にフラッシュする実装
 */
add_action( 'init', 'my_maybe_flush_rewrite_rules' );

function my_maybe_flush_rewrite_rules() {
    $current_version = '1.0.0';
    $saved_version   = get_option( 'my_plugin_rewrite_version', '' );

    if ( $current_version !== $saved_version ) {
        my_add_rewrite_rules();
        flush_rewrite_rules();
        update_option( 'my_plugin_rewrite_version', $current_version );
    }
}

注意事項

  • flush_rewrite_rules()initフックで毎回呼ばない: flush_rewrite_rules()は重い処理で、毎リクエストで実行するとデータベースへの余分なクエリが発生しパフォーマンスが大幅に低下します。有効化・無効化時のみ実行してください
  • 正規表現の^/?$: リライトルールの正規表現は^で始まり/?$または$で終わるパターンが基本です。サイトのルートからの相対パスにマッチします
  • 'top''bottom'の使い分け: 'top'はWordPressの標準ルールより優先され、'bottom'は後に追加されます。カスタムルールが標準ルールと競合する場合は'top'を使用してください
  • マルチサイトでの注意: マルチサイト環境ではリライトルールがネットワーク全体に影響する場合があります。サブサイトごとのルール追加には注意が必要です

まとめ

カスタムリライトルールの実装は、add_rewrite_rule()でURLパターンを定義し、add_rewrite_tag()またはquery_varsフィルターでクエリ変数を登録し、template_redirectでカスタム処理を行う3ステップで完成します。flush_rewrite_rules()はプラグインの有効化・無効化時のみ呼び出し、パフォーマンスへの影響を最小限に抑えることが重要です。関連記事:WordPressのWP_Queryを高度に活用する方法

お気軽にご相談ください

お見積りへ お問い合わせへ