# Un front headless affichait un contenu jamais publié dans WordPress : débogage

> Une revalidation ISR mal configurée servait aux visiteurs un contenu de brouillon jamais publié. Méthode de diagnostic et correctif du paramètre de prévisualisation.

- Auteur : Clément Hadrot
- Publié le : 2026-09-16
- Mis à jour le : 2026-09-16
- Catégorie : Headless &amp; API
- URL : https://wpmoderne.dev.wordpress-developpement.fr/headless/front-headless-contenu-jamais-publie-isr-debogage/

## L’essentiel

- Un jeton de prévisualisation mal isolé peut fuiter vers le rendu public d'une page
- Une revalidation déclenchée par erreur pendant l'édition d'un brouillon régénère la page publique avec un contenu non publié
- Séparer strictement les chemins de rendu public et de prévisualisation évite ce type de fuite

Le signalement, cette fois, venait directement de la rédactrice en chef d'un site d'actualité régionale : un article encore marqué comme brouillon dans WordPress, contenant des informations non vérifiées sur un fait divers en cours, s'affichait publiquement sur le site pendant plusieurs minutes avant de disparaître à nouveau. Un incident rare mais potentiellement grave pour la crédibilité éditoriale du média, et qui méritait un diagnostic rapide et complet, pas seulement un correctif de surface.

Le site fonctionnait avec Next.js en génération incrémentale statique (ISR), consommant WordPress headless via l'API REST, avec un système de prévisualisation de brouillon classique permettant aux journalistes de consulter un article avant publication via une route dédiée protégée par un jeton temporaire.

## Reconstituer la chronologie exacte

La première étape a consisté à reconstituer précisément ce qui s'était passé, à partir des journaux de déploiement de la plateforme d'hébergement et des journaux d'accès WordPress. La chronologie établie montrait que la journaliste avait ouvert le lien de prévisualisation de son brouillon dans un nouvel onglet pour le relire, exactement au moment où la tâche de revalidation programmée de la page publique correspondant au même slug s'est déclenchée en arrière-plan, un pur hasard de timing qui a suffi à révéler un défaut de conception plus profond.

## Le mécanisme de la fuite

La route de prévisualisation, construite selon un schéma courant sur les projets Next.js consommant WordPress, appelait l'API REST avec le paramètre `?status=draft` et un en-tête d'authentification transmettant un mot de passe d'application WordPress, pour récupérer le contenu non publié :

> L'essentiel à retenir : Un jeton de prévisualisation mal isolé peut fuiter vers le rendu public d'une page ; Une revalidation déclenchée par erreur pendant l'édition d'un brouillon régénère la page publique avec un contenu non publié ; Séparer strictement les chemins de rendu public et de prévisualisation évite ce type de fuite

```
// app/preview/[slug]/route.js (extrait simplifié)
export async function GET(req, { params }) {
  const { slug } = await params;
  const article = await fetch(
    `${API_URL}/wp/v2/posts?slug=${slug}&status;=draft,publish`,
    { headers: { Authorization: `Basic ${AUTH_TOKEN}` } }
  ).then((r) => r.json());
  return renderArticle(article[0], { preview: true });
}
```

Le défaut résidait dans une fonction utilitaire partagée, `recupererArticleParSlug()`, utilisée à la fois par la route de prévisualisation et par la fonction de génération statique de la page publique. Cette fonction avait été écrite pour accepter un paramètre optionnel `inclureDraft`, mais une modification récente, faite pour corriger un tout autre bug de cache, avait par erreur fixé la valeur par défaut de ce paramètre à `true` au lieu de `false` :

```
// Bug introduit par erreur lors d'une correction précédente
async function recupererArticleParSlug(slug, inclureDraft = true) {
  const statuts = inclureDraft ? 'draft,publish' : 'publish';
  // ...
}
```

Résultat : lorsque la tâche de revalidation ISR de la page publique s'est déclenchée pendant que le brouillon existait encore avec ce même slug, la fonction de génération statique a interrogé l'API avec `status=draft,publish` par défaut, récupéré le brouillon non publié faute de version publiée existante à ce slug, et régénéré la page statique publique avec ce contenu non validé, exposé à tous les visiteurs jusqu'à la revalidation suivante.

### Pourquoi ce bug est resté invisible en test

Les tests de non-régression existants sur ce projet couvraient bien la route de prévisualisation isolément et la génération de page publique isolément, mais aucun scénario ne testait leur interaction dans le cas précis d'un brouillon et d'une revalidation simultanés sur le même slug, un cas de figure temporellement rare mais pas impossible, comme cet incident l'a démontré.

## Le correctif appliqué

- Correction immédiate de la valeur par défaut du paramètre `inclureDraft`, remise à `false`, avec suppression de toute valeur par défaut implicite au profit d'un paramètre obligatoire explicite à chaque appel de la fonction, pour empêcher toute régression future du même type.
- Séparation stricte des deux fonctions, désormais dupliquées volontairement plutôt que partagées : une fonction dédiée exclusivement à la génération de page publique, qui ne peut techniquement jamais recevoir un paramètre l'autorisant à inclure un brouillon, et une fonction distincte pour la prévisualisation.
- Ajout d'un test d'intégration reproduisant précisément le scénario incriminé : un brouillon existant sur un slug donné, suivi d'un déclenchement de revalidation ISR sur ce même slug, avec une assertion vérifiant que le contenu public généré ne contient jamais le texte du brouillon.

```
test('la revalidation ISR ignore un brouillon existant sur le même slug', async () => {
  await creerBrouillon({ slug: 'test-fuite', titre: 'NE JAMAIS PUBLIER' });
  const html = await regenererPagePublique('test-fuite');
  expect(html).not.toContain('NE JAMAIS PUBLIER');
});
```

## La communication de crise, un volet trop souvent oublié

Au-delà du correctif technique, l'incident a été traité aussi comme un sujet éditorial : la rédaction a été informée précisément de la durée d'exposition réelle du contenu (six minutes, confirmées par les journaux), de la portée probable (le contenu n'ayant pas été indexé par un moteur de recherche ni partagé sur les réseaux sociaux durant cette fenêtre selon les outils de surveillance de mentions du média), pour permettre une décision éclairée sur l'opportunité de communiquer publiquement sur l'incident.

> Un mécanisme de prévisualisation et un mécanisme de rendu public qui partagent la moindre ligne de code méritent une vigilance particulière : le jour où ce partage se comporte différemment de prévu, c'est un contenu non validé qui se retrouve exposé publiquement, pas une simple erreur d'affichage sans conséquence.

## En résumé

Ce bug rare illustre un risque spécifique aux architectures headless combinant prévisualisation de brouillon et génération statique incrémentale : la mutualisation, en apparence raisonnable, d'une logique de récupération de données entre un contexte privé (prévisualisation) et un contexte public (page statique) crée un point de fragilité qui peut rester invisible pendant longtemps avant de se manifester dans des conditions de timing précises. La prévention la plus fiable reste la séparation stricte du code gérant ces deux contextes, même au prix d'une légère duplication, plutôt qu'une factorisation qui semble élégante mais fragilise la frontière entre contenu public et contenu privé.
