# no_found_rows=true dans WP_Query : le paramètre qui casse un sitemap maison

> Un paramètre de performance de WP_Query, très recommandé partout, désactive silencieusement le comptage total dont dépend la pagination d'un générateur de sitemap fait main.

- Auteur : WordPress Développement
- Publié le : 2023-12-18
- Mis à jour le : 2023-12-18
- Catégorie : SEO &amp; GEO
- URL : https://www.wpmoderne.fr/seo/no-found-rows-sitemap-maison-pagination/

## L’essentiel

- no_found_rows désactive found_posts et max_num_pages
- Un script de sitemap qui boucle sur max_num_pages s'arrête à la première page
- Il faut calculer le total autrement, avec un COUNT dédié

`'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.

> L'essentiel à retenir : no_found_rows désactive found_posts et max_num_pages ; Un script de sitemap qui boucle sur max_num_pages s'arrête à la première page ; Il faut calculer le total autrement, avec un COUNT dédié

## 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=count` en 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_rows` dans 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_pages` sans 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.
