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検証を必ず組み合わせます。