'no_found_rows' => true. Cette ligne traîne dans la moitié des tutoriels d’optimisation WordPress, et à raison : elle évite à MySQL de recalculer le nombre total de lignes correspondant à une requête, un calcul coûteux sur une grosse table wp_posts. Le problème survient quand ce même réglage se retrouve, par copier-coller, dans le script qui génère un sitemap personnalisé.
Un générateur de sitemap fait main a besoin de deux choses : la liste des articles d’une page, et le nombre total de pages pour savoir quand s’arrêter. La seconde information vient précisément de ce que no_found_rows désactive. Résultat : le script boucle une fois, ne trouve rien à itérer de plus, et livre un sitemap tronqué sans qu’aucune erreur ne remonte.
Ce que fait réellement no_found_rows
Quand ce paramètre vaut true, WordPress n’ajoute pas la clause SQL_CALC_FOUND_ROWS à la requête SQL générée par WP_Query. Concrètement, les propriétés found_posts et max_num_pages de l’objet requête restent à zéro, quelle que soit la quantité réelle de contenus correspondants. C’est un gain de performance réel et mesurable, surtout sur des tables de plusieurs dizaines de milliers de lignes, parce que SQL_CALC_FOUND_ROWS oblige MySQL à parcourir l’ensemble du jeu de résultats avant de le limiter avec LIMIT.
Le hic, c’est que ce comportement est documenté dans le Codex mais rarement lu jusqu’au bout par qui copie une configuration « performance » trouvée ailleurs. Un script de sitemap type ressemble à ceci :
$query = new WP_Query( array(
'post_type' => 'post',
'posts_per_page' => 500,
'paged' => $page,
'no_found_rows' => true,
) );
for ( $i = 1; $i <= $query->max_num_pages; $i++ ) {
// générer la page $i du sitemap
}
Avec no_found_rows à true, max_num_pages vaut systématiquement 0. La boucle for ne s’exécute jamais, ou une seule fois si la condition est mal écrite avec un do…while. Le sitemap final ne référence que la première tranche de cinq cents articles, et personne ne s’en aperçoit tant que le nombre total de contenus reste sous ce seuil.

Le bon calcul du nombre de pages
La solution consiste à séparer les deux besoins : une requête légère pour le comptage, une requête paginée pour le contenu. Pour le comptage, mieux vaut une requête SQL directe plutôt qu’un WP_Query complet, qui charge des objets WP_Post inutiles pour un simple total :
global $wpdb;
$total = (int) $wpdb->get_var(
"SELECT COUNT(ID) FROM {$wpdb->posts}
WHERE post_type = 'post' AND post_status = 'publish'"
);
$per_page = 500;
$total_pages = (int) ceil( $total / $per_page );
Ce total sert ensuite à piloter la boucle de génération, indépendamment du comportement de found_posts. Si l’on tient malgré tout à garder un seul objet WP_Query pour éviter deux systèmes de requêtage distincts, il suffit de passer no_found_rows à false uniquement pour la requête de comptage, quitte à payer le coût du SQL_CALC_FOUND_ROWS une seule fois par génération de sitemap plutôt qu’à chaque chargement de page publique.
Un piège voisin : le cache de found_posts
Autre subtilité : sur un site utilisant un cache d’objets persistant, found_posts peut aussi être mis en cache par des extensions tierces qui interceptent posts_results. Si le total change (nouvel article publié) sans que le cache soit invalidé, le sitemap continuera de se baser sur un compte obsolète. Un test simple : générer le sitemap juste après une publication et vérifier que le nombre d’URL a bien augmenté.
Vérifier avant de déployer
- Compter manuellement les articles publiés via
wp post list --post_type=post --post_status=publish --format=counten WP-CLI. - Comparer ce chiffre au nombre d’URL réellement présentes dans le sitemap généré.
- Si les deux ne correspondent pas, chercher
no_found_rowsdans le script avant toute autre piste. - Ajouter un test automatisé qui échoue si le nombre d’URL du sitemap descend sous un seuil connu.
Sur un sitemap fait main, ne jamais faire confiance à
max_num_pagessans avoir vérifié, au moins une fois, qu’il correspond au comptage manuel de la table. C’est un contrôle de deux minutes qui évite des semaines d’indexation partielle silencieuse.
En résumé
Le paramètre no_found_rows est une bonne pratique de performance, mais il ne doit jamais cohabiter, dans le même objet WP_Query, avec une logique de pagination qui dépend de max_num_pages. Pour un sitemap maison, la méthode la plus sûre reste de séparer explicitement le comptage total, via une requête SQL dédiée, de la récupération paginée des contenus. Ce découpage évite un bug silencieux qui n’apparaît que quand le catalogue dépasse la taille d’une seule page de résultats, c’est-à-dire précisément le moment où un sitemap devient réellement utile.