Nous developpons votre site vitrine SPA À PARTIR DE 300€. Oui, c'est dingue. Web SPA À PARTIR DE 300€ — Oui, c'est dingue. Parlons-en →

Comment prévenir Bing et Yandex d'un changement avec IndexNow sans attendre le crawl

Comment prévenir Bing et Yandex d'un changement avec IndexNow sans attendre le crawl

Publier une page et attendre qu'un moteur vienne la regarder est le comportement par défaut de presque tous les sites, et personne n'imagine que l'inverse soit possible. IndexNow renverse la logique : c'est vous qui prévenez, sur le moment. Voyons quels moteurs le consomment, ce qu'il vaut la peine de signaler et ce qu'il ne faut pas, et pourquoi le plus difficile n'est pas l'URL qui entre dans l'index mais celle qui en sort. Les exemples sont en Laravel, l'un de nos terrains quotidiens chez AndorraDev, mais le protocole est une simple requête HTTP et fonctionne depuis n'importe quel stack.

Google n'y participe pas, donc cela complète votre sitemap sans le remplacer

D'abord ceci, parce que cela conditionne les attentes : Google n'est pas dans IndexNow. Les moteurs qui le consomment sont Bing, Yandex, Seznam et Naver, et un seul envoi vers le point d'entrée partagé les atteint tous.

Cela dit, cela reste intéressant, pour deux raisons qui ne sont pas le volume de trafic de Bing.

Bing alimente plus de choses qu'il n'y paraît. Les assistants conversationnels qui citent leurs sources puisent dans son index, et là la fraîcheur compte bien plus que dans une recherche classique.

Et le modèle change de sens. Un sitemap est passif : il dit ce qui existe et attend qu'on vienne. IndexNow est actif : c'est vous qui prévenez au moment du changement. Un site dont le contenu expire (des fiches épuisées, des offres périmées, des articles dépubliés) présente un décalage entre ce qu'il affiche et ce que le moteur croit qu'il affiche, et c'est ce décalage que cela referme.

Tout le protocole tient dans un fichier et un POST

Il n'y a rien à apprendre. Vous publiez une clé sur votre domaine et vous envoyez les URL qui ont changé.

POST https://api.indexnow.org/indexnow
{
  "host": "votredomaine.com",
  "key": "a3f9c1...",
  "urlList": ["https://votredomaine.com/page-qui-a-change"]
}

Deux détails du protocole à connaître :

Une seule URL peut passer en GET avec la clé dans la chaîne de requête, et plusieurs passent en POST. Changer de forme selon le nombre n'est pas un caprice, c'est ce que le protocole autorise et cela évite une requête avec corps quand ce n'est pas nécessaire.

Le maximum est de dix mille URL par requête. Au-delà il faut découper, et ce découpage devrait être automatique : personne ne veut découvrir la limite en production.

La clé est publique à dessein, et c'est là le détail qui casse tout

La clé IndexNow n'est pas un secret. C'est un fichier que n'importe qui peut lire sur votre domaine, et son rôle n'est pas d'authentifier mais de prouver que celui qui envoie les URL contrôle le site.

Cela a une conséquence pratique très concrète : la clé envoyée dans la requête et le contenu du fichier doivent correspondre. Sinon, le moteur rejette l'envoi.

Et c'est là qu'apparaît le problème de déploiement. Si la clé vit dans une variable d'environnement et que le fichier est servi en la lisant, un serveur auquel cette variable manque sert un fichier qui ne correspond plus, et rien ne vous le dit jusqu'au premier envoi rejeté. D'où l'intérêt d'une commande qui vérifie réellement le fichier, en le téléchargeant par HTTP et en le comparant à la clé configurée. C'est la première chose à lancer quand quelque chose ne passe pas.

Il y a un autre choix qui mérite réflexion : servir le fichier comme une route ou comme un fichier physique. En route, c'est plus commode et multidomaine. En fichier physique, l'avantage n'apparaît que le jour où vous en avez besoin : artisan down répond 503 à toutes les routes, et un moteur qui vérifierait votre clé au milieu d'un déploiement conclurait qu'elle a disparu.

Andie recommande

L'hôte autorisé vient de votre URL de base, et ce filtre est précisément ce qui empêche un environnement de test de signaler des URL de production. Vérifiez que la variable pointe vers le bon domaine dans chaque environnement et vous aurez ce filet en place d'office, sans rien configurer d'autre.

La partie que presque personne n'implémente, c'est l'URL qui sort de l'index

Presque toutes les intégrations préviennent à la publication et s'arrêtent là, parce que c'est l'exemple fourni par le protocole et le cas auquel on pense en premier. Mais un index périmé gêne rarement par ce qui lui manque : il gêne par ce qui lui reste en trop, et cela ne se corrige qu'en signalant aussi ce qui disparaît.

Prévenir quand vous publiez, c'est l'évidence et tout le monde le fait. Ce que presque personne ne fait, c'est prévenir quand vous dépubliez, et c'est tout aussi important : si vous retirez une fiche sans le dire, le moteur continue d'afficher un résultat qui mène à une page qui n'existe plus.

Et il y a un troisième cas toujours oublié : le changement de slug. Là il faut envoyer deux URL, la nouvelle pour qu'elle soit indexée et l'ancienne pour qu'elle soit revue et écartée. Si vous n'envoyez que la nouvelle, l'ancienne reste dans l'index pendant des semaines.

Cela veut dire que la condition n'est pas "c'est publié", mais une transition : ce que c'était avant et ce que c'est maintenant. Entrée dans l'index, sortie de l'index, ou changement d'adresse.

Un observer naïf vous envoie cinquante mille URL d'un coup

Brancher le signalement sur l'événement de sauvegarde est la première ligne que tout le monde écrit, et elle tient sans une égratignure tant que les modifications viennent d'une personne devant un formulaire. Ce qui casse le montage, c'est le jour où elles viennent d'une machine, car la base de données se moque de qui écrit.

Si vous accrochez l'envoi à l'événement de sauvegarde sans plus, n'importe quel processus qui parcourt la table le déclenche. Et tout projet avec du volume en a un : une commande nocturne qui recalcule des statistiques, une réindexation, une migration de données.

Sur un catalogue à nous de 157 000 fiches, une commande de statistiques sauvegarde chaque enregistrement pour mettre à jour des compteurs. Sans aucune précaution, cette commande aurait envoyé d'un coup les plus de cinquante mille fiches indexables qui n'avaient encore jamais été signalées.

La solution consiste à déclarer quelles colonnes ne comptent pas comme un changement de contenu :

IndexNow::track(Game::class)
    ->url('games.show')
    ->when('published')
    ->ignoring([
        'gameplay_count_cache', 'stats_cache', 'rating_avg',
        'updated_at', 'last_played_at',
    ]);

Treize colonnes dans ce cas, toutes des compteurs réécrits par un processus automatique. La liste en place, cette commande ne génère plus un seul signalement.

Et le critère général s'applique facilement : si une colonne est écrite par un processus et non par une personne, ce n'est pas du contenu.

Un marqueur sur la ligne rend l'envoi idempotent

Une transition se produit une seule fois et ne revient pas. Si le signalement qui l'accompagnait se perd, aucune interrogation de la base ne le rattrapera : une table sait seulement où en sont les choses maintenant, jamais comment elles y sont arrivées.

Détecter la transition exacte fonctionne, mais impose de regarder au bon moment. Si le processus échoue, si le signalement se perd, ou si la ligne a changé d'état par une voie qui ne déclenche pas d'événements, cette transition ne se reproduira jamais.

Stocker une colonne du type index_now_sent change la question : au lieu de "vient-elle d'être publiée ?", elle devient "est-elle publiée et pas encore signalée ?". C'est un état, pas un instant, donc cela se vérifie quand vous voulez et le signalement devient idempotent.

Et cela offre gratuitement le rattrapage rétroactif, dont vous avez besoin le jour où vous installez cela sur un site qui a déjà du contenu : une commande qui parcourt la table par lots, envoie et marque, et qui, si elle est interrompue, reprend où elle en était.

Le 202 est un succès et le 403 signifie presque toujours la même chose

IndexNow répond avec des codes HTTP ordinaires mais leur donne des significations propres, sans accompagner aucun d'une explication : vous recevez le numéro et guère plus. Mieux vaut avoir la correspondance sous les yeux la première fois, car deux d'entre eux semblent exactement l'inverse de ce qu'ils sont.

202 est correct. Cela veut dire accepté, la clé étant encore en cours de vérification, et c'est normal les premières fois. Le traiter comme une erreur est le malentendu le plus fréquent.

403 concerne presque toujours le fichier de clé. Ce n'est pas un blocage : c'est que la clé n'est pas lisible publiquement ou ne correspond pas. Un message d'erreur utile devrait vous indiquer l'URL exacte où le fichier doit se trouver et ce qu'il doit contenir.

422 signifie que les URL n'appartiennent pas toutes à l'hôte que vous avez déclaré, ou que la clé ne correspond pas à cet hôte.

429 et les 5xx méritent un réessai, et le 429 apporte son propre Retry-After qu'il faut respecter.

400 ne se réessaie pas, car une URL malformée ne se corrige pas toute seule.

Cette distinction entre ce qui se réessaie et ce qui ne se réessaie pas, c'est la moitié du travail d'une intégration sérieuse. L'autre moitié est l'attente croissante entre les tentatives, de l'ordre d'une minute, cinq et quinze, la dernière tentative laissant l'échec enregistré avec son motif réel.

Les événements Eloquent ne voient pas le query builder

Il y a un trou dans toute intégration bâtie sur des observers, et aucun paquet ne le bouche parce que cela ne dépend pas de lui : Eloquent n'émet d'événements que lorsque l'écriture passe par le modèle. Tout ce qui va droit à la base est invisible.

Ces quatre opérations ne déclenchent pas d'événements de modèle :

Post::where('status', 'draft')->update(['status' => 'published']);
Post::where('created_at', '<', $date)->delete();
DB::table('posts')->insert($lignes);
Post::upsert($lignes, ['slug']);

Ce sont des écritures directes en base et aucun observer ne s'en aperçoit. Si votre processus d'import utilise upsert, ces pages ne seront jamais signalées par la voie automatique.

La bonne réponse est double : utilisez la commande de rattrapage après ces processus, ou, si vous avez la colonne marqueur, remettez-la à zéro dans l'UPDATE lui-même et laissez le flux normal les reprendre au passage suivant. La seconde est plus élégante parce qu'elle ne demande de se souvenir de rien.

Ce qui existe sur le marché et en quoi cela diffère

Il y a quatre chemins, et ce qui les distingue n'est pas la qualité de l'implémentation mais la part du problème qu'ils prennent en charge. Tous règlent bien l'envoi ; presque aucun ne touche à la décision du moment où il faut envoyer, qui est là où se trouve le vrai travail.

Les clients PHP purs vous donnent la requête HTTP et c'est tout. Le buffering, la file, la gestion de la clé, le regroupement par hôte et le découpage, vous les écrivez.

ymigval/laravel-indexnow et laravel-freelancer-nl/laravel-index-now sont les deux références côté Laravel. La seconde met en file avec un délai configurable et utilise l'unicité de job pour ne pas répéter. Toutes deux règlent bien le "comment envoyer". La différence d'approche à évaluer, c'est que le "quand envoyer" reste le vôtre : vous appelez l'envoi depuis où il faut, et décider quelle écriture de quel modèle mérite un signalement, et tout particulièrement les URL qui sortent de l'index, c'est le travail qui reste.

Le faire à la main tient en quinze lignes et se défend parfaitement si vous avez vingt pages. Avec du volume, ce que vous découvrez en chemin, c'est le chemin du fichier de clé, le regroupement par hôte, le découpage à dix mille, le 202 qui ressemble à une erreur, le 403 qui veut dire autre chose, le Retry-After, la déduplication et la commande nocturne qui vous envoie cinquante mille URL.

Et l'API Bing Webmaster Tools existe, avec son quota et sa propre clé. Elle ne couvre que Bing, alors qu'un envoi IndexNow atteint les quatre moteurs.

Ce qui a tranché dans notre cas, c'est de brancher le protocole sur les événements Eloquent avec des conditions de transition déclaratives, pour que publier prévienne et dépublier aussi, sans avoir à y penser à chaque endroit du code.

Et si vous ne travaillez pas avec Laravel

Les critères ne dépendent pas du langage :

  • Prévenez aussi quand la page sort de l'index, et envoyez l'ancienne URL en cas de changement de slug.
  • Décidez quelles colonnes comptent comme du contenu. Sinon, votre premier traitement de masse devient un envoi de masse.
  • Stockez un état, ne dépendez pas d'attraper l'instant. Cela rend le signalement idempotent et vous offre le rattrapage rétroactif.
  • Regroupez et attendez un peu avant d'envoyer. Une requête qui modifie trente pages devrait produire un envoi, pas trente.
  • Et lisez bien les codes : 202 est un succès, 403 concerne votre fichier de clé, et seuls 429 et les 5xx méritent un réessai.

Où est le code

Le paquet est publié sur Packagist, le code sur GitHub et la documentation du paquet recense les conditions de suivi, les commandes et la configuration. Il est sous licence MIT, avec six commandes de console et une table optionnelle pour l'historique des envois.

Si votre site a du contenu qui entre et sort de l'index, cela s'installe en une après-midi et referme le décalage entre ce que vous affichez et ce que le moteur croit que vous affichez. Nous travaillons cela dans référencement web et développement Laravel, et si vous nous dites ce que vous publiez et à quelle fréquence, nous vous dirons si cela vaut le coup.

Écrit par
Edu Lazaro
Edu Lazaro
Founder & Lead Developer en AndorraDev

Développeur full-stack avec plus de 15 ans d'expérience en Laravel, React, Node.js et architectures cloud. J'aide les entreprises en Andorre à construire leur présence digitale.

Partner de diseño · ionospace.
Besoin d'aide? ×
Andie by AndorraDev
Assistant IA + équipe humaine
Assistant IA d'AndorraDev
Andie
Bonjour! Je suis Andie, l'assistant IA d'AndorraDev. Comment puis-je vous aider? Si vous souhaitez parler avec Edu, il suffit de le demander.
17:28