Plugin Grav IndexNow : soumettre automatiquement ses pages aux moteurs de recherche

⚡ En bref
Grav ne dispose d’aucun plugin IndexNow dans son catalogue officiel. Ce plugin maison comble ce manque en soumettant automatiquement les URLs modifiées à api.indexnow.org à chaque sauvegarde — que ce soit via l’admin Grav ou via le plugin MCP — sans intervention manuelle, sans cron, sans dépendance externe.
Le code est disponible sur GitHub :
- 🔌 Plugin : jmrGrav/grav-plugin-indexnow
🧠 Pourquoi
IndexNow est un protocole ouvert permettant de notifier instantanément les moteurs de recherche compatibles (Bing, Yandex) qu’une page a été créée ou modifiée. Sans lui, les moteurs attendent leur prochain passage de crawler — qui peut prendre des heures ou des jours.
Plusieurs raisons ont motivé ce développement :
- Aucun plugin natif : contrairement à WordPress ou Shopify, Grav n’a pas de plugin IndexNow dans son catalogue
- Cloudflare Crawler Hints déjà actif mais limité aux invalidations de cache — pas aux modifications de contenu
- Contrôle total : soumettre explicitement les 3 variantes d’URL (
/route,/fr/route,/en/route) à chaque save - Traçabilité : chaque soumission est loggée dans
grav.logavec le code HTTP retourné
🔧 Ce qui a été fait
📁 Structure du plugin
Un plugin Grav minimal se compose de 3 fichiers :
/var/www/grav/user/plugins/indexnow/
├── indexnow.php ← logique principale
├── blueprints.yaml ← définition des champs admin
└── indexnow.yaml ← valeurs par défaut🔍 Trouver le bon hook admin
Le premier obstacle a été d’identifier l’événement Grav déclenché lors d’un save dans l’admin. Le hook onPageSave — intuitif mais inexistant — ne fonctionne pas. La recherche dans le code source a révélé le bon candidat :
grep -rh "fireEvent(" /var/www/grav/system/src/ | sort -u
# → onAdminAfterSave émis par AdminController.php après chaque save réussi| Hook testé | Résultat |
|---|---|
onPageSave | ❌ N’existe pas dans Grav admin |
onAdminSave | ⚠️ Émis avant la validation |
onAdminAfterSave | ✅ Émis après chaque save réussi |
⚙️ Code du plugin
public static function getSubscribedEvents(): array
{
return [
'onAdminAfterSave' => ['onAdminAfterSave', 0],
'onMcpAfterSave' => ['onMcpAfterSave', 0],
];
}🐛 Bug 1 : $this->config() vs $this->grav['config']
La méthode $this->config() dans un plugin Grav retourne un array, pas un objet Config. Appeler ->get() dessus provoque une erreur fatale :
// ❌ Incorrect — retourne un array, pas un objet Config
$config = $this->config();
$key = $config->get('plugins.indexnow.key');
// ✅ Correct
$config = $this->grav['config'];
$key = $config->get('plugins.indexnow.key');🐛 Bug 2 : le plugin MCP bypasse le hook admin
Après les premiers tests, un problème est apparu : les pages créées ou modifiées via le plugin MCP (create_page, update_page) ne déclenchaient pas onAdminAfterSave. Ce hook n’est émis que par PageObject.php dans le contexte admin — les appels programmatiques via MCP le bypasse complètement.
Diagnostic :
# Aucun fireEvent dans le plugin MCP
grep -r "fireEvent" /var/www/grav/user/plugins/mcp-server/mcp-server.php
# → (aucun résultat)
# onAdminAfterSave conditionné à isAdminSite()
grep -r "onAdminAfterSave" /var/www/grav/system/src/Grav/Common/Flex/Types/Pages/PageObject.php
# → if ($this->isAdminSite()) { $grav->fireEvent('onAdminAfterSave', ...) }Solution — event custom onMcpAfterSave :
Plutôt que de réutiliser onAdminAfterSave (qui nécessite un objet PageInterface pas toujours disponible après un write MCP), un event custom a été ajouté directement dans le plugin MCP après chaque opération réussie :
// Dans mcp-server.php — helper ajouté après toolCreatePage et toolUpdatePage
private function notifyPageSaved(string $route): void
{
$grav = \Grav\Common\Grav::instance();
$grav->fireEvent('onMcpAfterSave', new \RocketTheme\Toolbox\Event\Event([
'route' => $route,
]));
}Le plugin IndexNow écoute cet event et extrait la route directement depuis le payload :
public function onMcpAfterSave(Event $event): void
{
$route = $event['route'] ?? null;
if (!$route) return;
if (in_array($route, self::EXCLUDED_ROUTES, true)) return;
if (str_starts_with($route, '/tag')) return;
$this->submitToIndexNow($route);
}✅ Résultat final — deux contextes couverts
| Contexte | Hook | Statut |
|---|---|---|
| Save via admin Grav | onAdminAfterSave | ✅ Actif |
create_page / update_page via MCP | onMcpAfterSave | ✅ Actif |
📋 Fichier de clé IndexNow
Le fichier clé doit être accessible publiquement à la racine du site :
curl -I https://arleo.eu/bf6faf5563914fe7bb18d429976b182d.txt
# → HTTP/2 200✅ Vérification dans les logs
tail -f /var/www/grav/logs/grav.log | grep -i indexnow
# [IndexNow] ✅ Soumission OK (HTTP 200) pour :
# https://arleo.eu/fr/grav-plugin-indexnow,
# https://arleo.eu/en/grav-plugin-indexnow,
# https://arleo.eu/grav-plugin-indexnow🏁 Conclusion
Le plugin couvre désormais les deux contextes de modification : l’admin Grav via onAdminAfterSave et le plugin MCP via l’event custom onMcpAfterSave. Chaque sauvegarde déclenche une soumission immédiate des 3 variantes d’URL à IndexNow, avec traçabilité complète dans grav.log. Le délai d’indexation par Bing passe de plusieurs jours à quelques minutes.
Pour aller plus loin :
- 💡 Ajouter une soumission bulk au démarrage du plugin pour indexer toutes les pages existantes en une seule requête
- 💡 Étendre la soumission à d’autres moteurs compatibles IndexNow (Naver, Seznam) via leurs endpoints dédiés