# ISR et revalidation par webhooks : garder un site headless WordPress à jour

> Combinez l'ISR de Next.js et un webhook WordPress déclenché sur save_post pour un site statique toujours à jour, sans rebuild complet à chaque publication.

- Auteur : Clément Hadrot
- Publié le : 2023-02-09
- Mis à jour le : 2023-02-09
- Catégorie : Headless &amp; API
- URL : https://wpmoderne.dev.wordpress-developpement.fr/headless/isr-revalidation-webhooks-wordpress-headless/

## L’essentiel

- save_post déclenche un appel HTTP vers Next.js à chaque publication
- res.revalidate() régénère une seule page, pas tout le site
- Un secret partagé protège la route de revalidation

L'ISR (Incremental Static Regeneration), introduite par Next.js dès sa version 9.5, permet à une page statique de se régénérer automatiquement après un délai défini, sans reconstruire l'intégralité du site. C'est un net progrès par rapport à un export statique classique, mais son fonctionnement par défaut reste passif : la régénération n'a lieu qu'après l'expiration du délai et seulement lorsqu'une requête arrive sur la page concernée.

Pour un site éditorial où la fraîcheur du contenu compte, ce comportement passif ne suffit pas toujours : un rédacteur qui publie un article s'attend à le voir apparaître immédiatement, pas après le délai de revalidation configuré. La solution : déclencher la revalidation à la demande, directement depuis WordPress, via un webhook au moment de la publication.

## Le principe général

L'idée consiste à connecter deux systèmes indépendants par un appel HTTP : WordPress détecte qu'un article vient d'être publié ou modifié, et notifie immédiatement le frontend Next.js pour qu'il régénère la page concernée, sans attendre le délai de revalidation passive.

## Côté WordPress : déclencher le webhook

Le hook `save_post` se déclenche à chaque enregistrement d'un article, y compris pour les brouillons. Il faut donc filtrer précisément les cas pertinents, généralement les publications au statut `publish` :

```
add_action( 'save_post', function( $post_id, $post, $update ) {
    if ( $post->post_status !== 'publish' ) {
        return;
    }

    if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) {
        return;
    }

    $reponse = wp_remote_post( 'https://frontend.exemple.fr/api/revalidate', array(
        'timeout' => 5,
        'body'    => array(
            'secret' => WEBHOOK_REVALIDATE_SECRET,
            'slug'   => $post->post_name,
        ),
    ) );

    if ( is_wp_error( $reponse ) ) {
        error_log( 'Échec du webhook de revalidation : ' . $reponse->get_error_message() );
    }
}, 10, 3 );
```

Le secret partagé, défini en constante dans `wp-config.php`, empêche quiconque connaissant l'URL de la route de revalidation de déclencher des régénérations arbitraires sur votre frontend.

> L'essentiel à retenir : save_post déclenche un appel HTTP vers Next.js à chaque publication ; res.revalidate() régénère une seule page, pas tout le site ; Un secret partagé protège la route de revalidation

## Côté Next.js : la route de revalidation

Sur le Pages Router de Next.js, une route API dédiée reçoit l'appel de WordPress, vérifie le secret, puis déclenche la régénération ciblée via `res.revalidate()` :

```
// pages/api/revalidate.js
export default async function handler( req, res ) {
  if ( req.body.secret !== process.env.WEBHOOK_REVALIDATE_SECRET ) {
    return res.status( 401 ).json( { message: 'Secret invalide' } );
  }

  try {
    await res.revalidate( `/articles/${ req.body.slug }` );
    await res.revalidate( '/' );
    return res.json( { revalidated: true } );
  } catch ( erreur ) {
    return res.status( 500 ).send( 'Erreur lors de la revalidation' );
  }
}
```

Notez que la revalidation cible explicitement deux pages ici : la page de l'article modifié, et la page d'accueil, qui affiche probablement les derniers articles publiés et doit donc aussi refléter le changement. C'est un point à ne pas négliger : la revalidation ne se propage pas automatiquement aux pages qui référencent indirectement le contenu modifié.

## Gérer les cas d'échec

Un webhook peut échouer pour de multiples raisons : frontend temporairement indisponible, timeout réseau, erreur de configuration du secret. Il est important que cet échec ne bloque jamais la publication de l'article côté WordPress : c'est pour cette raison que l'appel utilise `wp_remote_post` avec un `timeout` court, et que l'échec est simplement journalisé plutôt que de faire échouer la publication elle-même.

- Toujours définir un `timeout` raisonnable sur l'appel HTTP sortant, pour ne jamais bloquer l'enregistrement de l'article
- Journaliser les échecs pour pouvoir déclencher une revalidation manuelle en cas de problème récurrent
- Prévoir malgré tout une revalidation passive (délai `revalidate` classique) en filet de sécurité, au cas où le webhook échouerait silencieusement

> Ne comptez jamais uniquement sur le webhook pour la fraîcheur du contenu. Gardez toujours un délai de revalidation passive en complément, ne serait-ce que pour rattraper les cas où le webhook n'a pas abouti, un réseau instable en environnement de production reste plus fréquent qu'on ne le pense.

## Pour l'App Router : la même logique, une API différente

Depuis l'introduction de l'App Router, Next.js propose une fonction équivalente, `revalidatePath`, appelée depuis une route handler plutôt qu'une API Pages classique. Le principe de sécurisation par secret partagé reste identique ; seule la syntaxe change. Nous détaillerons cette approche dans un prochain article consacré spécifiquement à l'App Router.

## En résumé

Combiner l'ISR passive de Next.js à une revalidation à la demande déclenchée par webhook depuis WordPress offre le meilleur compromis pour un site headless : la performance d'un contenu statique, avec une fraîcheur quasi immédiate au moment de la publication. La mise en place reste simple, à condition de sécuriser l'appel avec un secret partagé et de toujours prévoir un filet de sécurité en cas d'échec du webhook.
