В WooCommerce чаще всего ломается не сама индексация, а логика обновления. Цена поменялась, остаток ушёл в ноль, товар снова появился в наличии — а поисковик ещё какое-то время видит старую версию страницы. Если на сайте уже настроен IndexNow, это можно использовать точечно: отправлять URL только в тех сценариях, где изменился коммерческий сигнал, а не при каждом сохранении карточки.
Ниже — рабочая схема для магазина, где важны цена, наличие и варианты товара. Без лишней автоматизации, но с нормальной диагностикой, проверкой результата и типичными ошибками, которые обычно всплывают на реальных проектах.
Когда проблема действительно в обновлении URL, а не в кэше
Перед тем как трогать код, стоит понять, где именно задержка. В WooCommerce часто путают три разных слоя: кэш страницы, кэш объекта и фактическую отправку сигнала в поисковые системы. IndexNow не исправляет кэш сам по себе — он только сообщает о смене URL. Если страница на фронте уже обновилась, а в поиске ещё старая цена, значит проблема не в шаблоне, а в скорости переобхода.
Признаки, что нужен именно IndexNow
- цена или наличие меняются часто, особенно в акциях и распродажах;
- карточки товаров индексируются, но обновления подтягиваются с задержкой;
- есть вариативные товары, где меняются остатки у конкретных вариаций;
- на сайте много товаров, и вручную отправлять URL через панель неудобно;
- нужно отправлять не весь сайт, а только изменившиеся карточки.
Что проверить до внедрения
- товар реально меняет публичный URL или только метаданные;
- на сайте нет агрессивного кэша, который скрывает обновление на фронте;
- ключ IndexNow уже размещён и доступен по ожидаемому адресу;
- сервер не блокирует исходящие запросы к API поисковиков;
- обновление цены и остатка происходит через стандартные хуки WooCommerce, а не через кастомный импорт без событий.
Какой сценарий отправки выбрать: плагин, код или гибрид
Если магазин небольшой, проще использовать готовое решение. Если изменения идут массово или есть нестандартная логика остатков, лучше оставить отправку в коде и ограничить её нужными событиями. Для больших каталогов гибридный вариант обычно практичнее: код фиксирует изменения, а очередь или cron отправляет URL пакетами.
| Подход | Когда подходит | Компромисс |
|---|---|---|
| Плагин | Небольшой магазин, стандартные товары | Меньше контроля над условиями отправки |
| Код в теме или мини-плагине | Нужна точная логика по цене и наличию | Нужно поддерживать код при обновлениях |
| Гибрид: код + очередь | Много товаров, массовые изменения, импорт | Сложнее настройка, но меньше нагрузка |
Пошаговое решение: отправлять URL только при изменении цены и остатка
Ниже пример, который можно положить в мини-плагин или в functions.php дочерней темы. Он отслеживает сохранение товара, сравнивает старые и новые значения цены и наличия, и только потом вызывает отправку URL в IndexNow через HTTP-запрос.
Важно: код ниже не привязан к вымышленному API. Он использует обычный wp_remote_post() и формат JSON, который ожидают сервисы IndexNow. Перед использованием проверьте актуальный endpoint и требования конкретной поисковой системы, если вы отправляете не только в общий IndexNow-совместимый поток.
<?php
add_action( 'woocommerce_update_product', 'ixn_send_product_url_on_stock_or_price_change', 10, 1 );
function ixn_send_product_url_on_stock_or_price_change( $product_id ) {
$product = wc_get_product( $product_id );
if ( ! $product ) {
return;
}
$old_price = get_post_meta( $product_id, '_ixn_old_price', true );
$old_stock = get_post_meta( $product_id, '_ixn_old_stock_status', true );
$new_price = $product->get_price();
$new_stock = $product->get_stock_status();
$changed = false;
if ( (string) $old_price !== (string) $new_price ) {
$changed = true;
}
if ( (string) $old_stock !== (string) $new_stock ) {
$changed = true;
}
update_post_meta( $product_id, '_ixn_old_price', $new_price );
update_post_meta( $product_id, '_ixn_old_stock_status', $new_stock );
if ( ! $changed ) {
return;
}
$url = get_permalink( $product_id );
$endpoint = 'https://api.indexnow.org/indexnow';
$body = array(
'host' => wp_parse_url( home_url(), PHP_URL_HOST ),
'key' => 'YOUR_INDEXNOW_KEY',
'keyLocation' => home_url( '/YOUR_INDEXNOW_KEY.txt' ),
'urlList' => array( $url ),
);
$response = wp_remote_post( $endpoint, array(
'timeout' => 15,
'headers' => array( 'Content-Type' => 'application/json; charset=utf-8' ),
'body' => wp_json_encode( $body ),
) );
if ( is_wp_error( $response ) ) {
error_log( 'IndexNow error for product ' . $product_id . ': ' . $response->get_error_message() );
return;
}
$code = wp_remote_retrieve_response_code( $response );
if ( $code < 200 || $code >= 300 ) {
error_log( 'IndexNow HTTP ' . $code . ' for product ' . $product_id );
}
}Этот вариант годится как отправная точка, но у него есть слабое место: он хранит старое значение в метаполях и сравнивает его при следующем сохранении. На проектах с импортом и массовыми обновлениями лучше не полагаться только на это. Надёжнее сравнивать исходные данные до и после сохранения или складывать URL в очередь.
Если нужно учитывать вариации товара
У вариативных товаров остаток и цена часто живут не на родительской записи, а на вариациях. В таком случае отправлять нужно не только URL родителя, но и проверять, менялась ли конкретная вариация. Если на витрине используется одна карточка с переключением вариаций, обычно достаточно отправить URL родителя, потому что именно он индексируется как основная страница товара.
<?php
add_action( 'woocommerce_save_product_variation', 'ixn_send_parent_url_on_variation_change', 10, 2 );
function ixn_send_parent_url_on_variation_change( $variation_id, $i ) {
$variation = wc_get_product( $variation_id );
if ( ! $variation ) {
return;
}
$parent_id = $variation->get_parent_id();
if ( ! $parent_id ) {
return;
}
$parent_url = get_permalink( $parent_id );
// Здесь можно вызвать общую функцию отправки URL в IndexNow.
// Отправляйте URL родителя, если именно он является канонической страницей товара.
}Как не перегрузить сайт при массовом обновлении цен
Если цены меняются импортом или через прайс-лист поставщика, отправлять запрос на каждый товар сразу — плохая идея. На живом магазине это создаёт лишнюю нагрузку и может упереться в лимиты внешнего API. Лучше складывать URL в очередь и отправлять пакетами через wp_cron или системный cron. Для IndexNow это особенно полезно, когда за один импорт обновляются сотни карточек.
Практически это выглядит так: при сохранении товара вы не вызываете внешний запрос сразу, а записываете URL в массив или transient, затем отдельная задача раз в минуту отправляет пачку из нескольких URL. Такой подход проще контролировать и легче отлаживать.
Минимальная схема очереди
<?php
add_action( 'save_post_product', 'ixn_queue_product_url', 10, 3 );
function ixn_queue_product_url( $post_id, $post, $update ) {
if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) {
return;
}
$queue = get_option( 'ixn_url_queue', array() );
$queue[] = get_permalink( $post_id );
$queue = array_values( array_unique( array_filter( $queue ) ) );
update_option( 'ixn_url_queue', $queue, false );
}
add_action( 'ixn_send_queue_event', 'ixn_send_queue_batch' );
function ixn_send_queue_batch() {
$queue = get_option( 'ixn_url_queue', array() );
if ( empty( $queue ) ) {
return;
}
$batch = array_slice( $queue, 0, 10 );
$remaining = array_slice( $queue, 10 );
$body = array(
'host' => wp_parse_url( home_url(), PHP_URL_HOST ),
'key' => 'YOUR_INDEXNOW_KEY',
'keyLocation' => home_url( '/YOUR_INDEXNOW_KEY.txt' ),
'urlList' => $batch,
);
$response = wp_remote_post( 'https://api.indexnow.org/indexnow', array(
'timeout' => 15,
'headers' => array( 'Content-Type' => 'application/json; charset=utf-8' ),
'body' => wp_json_encode( $body ),
) );
if ( ! is_wp_error( $response ) && wp_remote_retrieve_response_code( $response ) >= 200 && wp_remote_retrieve_response_code( $response ) < 300 ) {
update_option( 'ixn_url_queue', $remaining, false );
}
}Если у вас уже есть очередь в другом плагине или в собственной системе импорта, не дублируйте логику. Достаточно одного места, где URL собираются, и одного места, где они уходят наружу.
Проверка результата после внедрения
Проверять нужно не только факт HTTP-ответа, но и то, что URL действительно попал в нужный сценарий. На практике я смотрю на три уровня: лог отправки, ответ сервера и поведение страницы после изменения.
- после изменения цены в логах появляется запись об отправке;
- HTTP-ответ от endpoint не содержит ошибки сети или 4xx/5xx;
- URL карточки остаётся тем же, если не менялся slug;
- страница товара обновляется на фронте без задержки кэша;
- при повторном сохранении без изменений запрос не уходит.
Если хотите быстро проверить руками, временно добавьте логирование в отправляющую функцию и посмотрите debug.log. Для этого в wp-config.php должны быть включены WP_DEBUG и WP_DEBUG_LOG. После теста логирование лучше оставить только для ошибок, а не для каждого товара.
Что считать успешным результатом
Успех — это не только 200 OK от внешнего API. Важнее, чтобы отправка происходила именно в момент изменения цены или остатка, а не при каждом обновлении записи. Если товар обновляется без изменения коммерческих данных, запрос не должен уходить. Если меняется вариация, а карточка родителя остаётся той же, отправка должна быть привязана к каноническому URL товара.
Частые ошибки и как их исправить
Отправка идёт при каждом сохранении товара
Обычно причина в том, что код сравнивает не старое и новое значение, а просто реагирует на хук сохранения. Исправление простое: сравнивайте цену и статус наличия до и после обновления, либо используйте очередь с дедупликацией URL.
URL уходит, но в поиске ничего не меняется
IndexNow не гарантирует мгновенную переиндексацию. Если страница закрыта от индексации, имеет неправильный canonical или отдаёт ошибку, сигнал не поможет. Сначала проверьте, что товар доступен для обхода, а потом уже смотрите на скорость обновления.
Сыпятся ошибки 403 или 404 на keyLocation
Частая причина — файл с ключом лежит не в корне сайта или закрыт правилами сервера. Проверьте, что файл доступен по адресу, который указан в запросе, и что он отдается без редиректов и авторизации.
Массовый импорт подвешивает сайт
Значит, запрос отправляется синхронно в момент сохранения каждого товара. Переводите отправку в очередь, ограничивайте размер пачки и не делайте внешний HTTP-запрос внутри тяжёлого цикла импорта.
У вариаций меняется остаток, но отправляется не тот URL
Вариации часто имеют собственные ID, но в индексе важен URL родительской карточки. Если отправлять URL вариации, это может быть бесполезно. Проверьте, какая страница реально индексируется и какая является канонической.
Безопасность и производительность: что не стоит игнорировать
Ключ IndexNow не является секретом уровня пароля администратора, но его всё равно не стоит разбрасывать по публичным репозиториям и логам. Если вы храните ключ в коде, убедитесь, что файл не попадает в git-репозиторий без необходимости. Для production-проектов лучше держать его в конфиге или в отдельном мини-плагине с ограниченным доступом.
По производительности важнее другое: не отправляйте запросы без фильтрации. Если товар меняет только служебное поле, а цена и остаток не тронуты, внешний запрос не нужен. Это особенно заметно на магазинах с частыми правками контента, где лишние вызовы просто засоряют логи и увеличивают время сохранения записи.
Если на сайте уже есть плагин для чистки дублей и технической оптимизации, например Clearfy Pro, проверьте, не конфликтует ли он с вашей логикой canonical и robots.txt. Для IndexNow это не обязательное условие, но на реальных магазинах такие настройки часто влияют на то, как быстро и корректно поисковик воспринимает обновлённую карточку товара.
Короткий чек-лист перед запуском в продакшн
- ключ IndexNow доступен по
keyLocationбез редиректов; - отправка привязана к изменению цены или статуса наличия, а не к любому сохранению;
- для вариативных товаров выбрана правильная каноническая страница;
- массовые обновления идут через очередь, а не синхронно;
- включено логирование ошибок, но не избыточный debug для каждого запроса;
- страницы товаров не закрыты от индексации и не ломаются кэшем.
Если после внедрения карточки товаров начинают обновляться в поиске заметно быстрее, значит схема собрана правильно. Если нет — сначала ищите проблему в доступности страницы, canonical и кэше, а уже потом в самом IndexNow. В магазинах это почти всегда быстрее приводит к результату, чем попытка «усилить» отправку дополнительными запросами.