2026年5月20日

2026年5月20日

WordPressの検索結果ページをカスタマイズする方法

はじめに

WordPressの検索結果ページは search.php テンプレートで制御します。検索キーワードのハイライト・対象投稿タイプの絞り込み・Ajax検索など、UXを高めるカスタマイズを解説します。

症状・原因

  • 検索結果ページのデザインをカスタマイズしたい
  • 特定の投稿タイプだけを検索対象にしたい
  • 検索キーワードをハイライトしたい
  • リアルタイム検索(Ajax)を実装したい

解決手順

ステップ1:search.phpを作成する

// search.php
get_header();
?>
<main id="main" class="site-main">

    <header class="page-header search-header">
        <h1 class="page-title">
            <?php
            printf(
                '「%s」の検索結果',
                '<span class="search-query">' . esc_html(get_search_query()) . '</span>'
            );
            ?>
        </h1>

        <?php if (have_posts()) : ?>
            <p class="search-count">
                <?php printf('%d件見つかりました', $wp_query->found_posts); ?>
            </p>
        <?php endif; ?>

        <!-- 検索フォーム -->
        <?php get_search_form(); ?>
    </header>

    <?php if (have_posts()) : ?>
        <div class="search-results">
            <?php while (have_posts()) : the_post(); ?>
                <article class="search-result-item">
                    <h2 class="entry-title">
                        <a href="<?php the_permalink(); ?>">
                            <?php the_title(); ?>
                        </a>
                    </h2>
                    <div class="entry-meta">
                        <time><?php echo get_the_date(); ?></time>
                        <span class="post-type"><?php echo get_post_type_object(get_post_type())->labels->singular_name; ?></span>
                    </div>
                    <div class="entry-excerpt">
                        <?php the_excerpt(); ?>
                    </div>
                </article>
            <?php endwhile; ?>
        </div>

        <?php the_posts_pagination(); ?>

    <?php else : ?>
        <div class="no-results">
            <p>「<?php echo esc_html(get_search_query()); ?>」に一致する結果は見つかりませんでした。</p>
            <p>別のキーワードでお試しください。</p>
            <?php get_search_form(); ?>
        </div>
    <?php endif; ?>

</main>
<?php get_footer();

ステップ2:検索対象を絞り込む

// functions.php
function mytheme_search_filter(WP_Query $query): void {
    if (is_admin() || !$query->is_main_query() || !$query->is_search()) {
        return;
    }

    // 検索対象の投稿タイプを限定
    $query->set('post_type', ['post', 'news', 'page']);

    // 特定カテゴリを除外
    $query->set('category__not_in', [5]); // ID:5のカテゴリを除外

    // 下書きを除外(デフォルトで除外されているが明示)
    $query->set('post_status', 'publish');

    // 1ページあたりの件数
    $query->set('posts_per_page', 10);
}
add_action('pre_get_posts', 'mytheme_search_filter');

ステップ3:検索キーワードをハイライトする

// functions.php
function mytheme_highlight_search_terms(string $text): string {
    if (!is_search()) {
        return $text;
    }

    $query = get_search_query();
    if (empty($query)) {
        return $text;
    }

    // キーワードをスペースで分割して各単語をハイライト
    $words = preg_split('/[\s\x{3000}]+/u', $query, -1, PREG_SPLIT_NO_EMPTY);

    foreach ($words as $word) {
        $text = preg_replace(
            '/(' . preg_quote(esc_html($word), '/') . ')/iu',
            '<mark class="search-highlight">$1</mark>',
            $text
        );
    }

    return $text;
}
add_filter('the_excerpt', 'mytheme_highlight_search_terms');
add_filter('the_title', 'mytheme_highlight_search_terms');
/* style.css */
.search-highlight {
    background: #fef08a;
    color: inherit;
    padding: 0 0.1em;
    border-radius: 2px;
}

ステップ4:Ajax検索を実装する

// functions.php
function mytheme_ajax_search(): void {
    check_ajax_referer('mytheme_search_nonce', 'nonce');

    $query = sanitize_text_field($_POST['query'] ?? '');
    if (empty($query) || mb_strlen($query) < 2) {
        wp_send_json_error(['message' => '2文字以上入力してください']);
    }

    $results = new WP_Query([
        's'              => $query,
        'post_type'      => ['post', 'news'],
        'posts_per_page' => 5,
        'post_status'    => 'publish',
    ]);

    $items = [];
    if ($results->have_posts()) {
        while ($results->have_posts()) {
            $results->the_post();
            $items[] = [
                'title'     => get_the_title(),
                'url'       => get_permalink(),
                'excerpt'   => wp_trim_words(get_the_excerpt(), 20),
                'thumbnail' => get_the_post_thumbnail_url(null, 'thumbnail'),
            ];
        }
        wp_reset_postdata();
    }

    wp_send_json_success(['items' => $items]);
}
add_action('wp_ajax_mytheme_search', 'mytheme_ajax_search');
add_action('wp_ajax_nopriv_mytheme_search', 'mytheme_ajax_search');
// js/search.js
const searchInput = document.getElementById('ajax-search-input');
const resultBox   = document.getElementById('ajax-search-results');
let debounceTimer;

searchInput?.addEventListener('input', () => {
    clearTimeout(debounceTimer);
    debounceTimer = setTimeout(async () => {
        const query = searchInput.value.trim();
        if (query.length < 2) { resultBox.innerHTML = ''; return; }

        const formData = new FormData();
        formData.append('action', 'mytheme_search');
        formData.append('query', query);
        formData.append('nonce', mythemeSearch.nonce);

        const res  = await fetch(mythemeSearch.ajaxUrl, { method: 'POST', body: formData });
        const data = await res.json();

        if (data.success && data.data.items.length) {
            resultBox.innerHTML = data.data.items.map(item =>
                `<a href="${item.url}" class="search-suggestion">${item.title}</a>`
            ).join('');
        } else {
            resultBox.innerHTML = '<p class="no-results">見つかりませんでした</p>';
        }
    }, 300); // 300ms デバウンス
});

注意事項

  • 検索ハイライト処理は the_title フィルターを使うと、属性値にも が入る場合があります。wp_title や属性内での使用には注意してください
  • Ajax検索はデバウンス(入力後300ms待機)を必ず入れて、過剰なリクエストを防いでください
  • check_ajax_referer でnonce検証を行い、CSRF攻撃を防ぎます

まとめ

search.php で検索結果を表示し、get_search_query() でキーワード取得、$wp_query->found_posts で件数を表示します。pre_get_posts で投稿タイプ・件数を制御し、ハイライトは the_excerpt フィルターで実装します。Ajax検索はデバウンスとnonce検証を必ず組み合わせます。

お気軽にご相談ください

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