ISR avec Strapi & Next.js : guide pour une génération statique
Apprenez à combiner l’Incremental Static Regeneration de Next.js avec le plugin Revisionary de Strapi pour servir du contenu à jour, respecter les verrous éditoriaux et garder l’historique complet des versions.
Par l’équipe Revisionary · · 5 min de lecture
L’Incremental Static Regeneration (ISR) permet à un site Next.js de servir des pages pré‑rendu et de les mettre à jour en arrière‑plan sans reconstruction complète. Lorsque la source de données est un CMS headless comme Strapi, il faut synchroniser le déclenchement de la régénération avec le flux éditorial afin que les brouillons non publiés, les verrous d’édition ou les restaurations de contenu ne soient pas servies accidentellement. Ce guide explique comment connecter l’ISR au plugin Revisionary de Strapi, récupérer uniquement les versions publiées et garder l’historique complet des versions disponible pour l’audit et le rollback.
1. Prérequis
| Exigence | Détails |
|---|---|
| Instance Strapi | Strapi en cours d’exécution avec le plugin Revisionary installé (voir le guide Install) |
| Projet Next.js | Utilisant TypeScript, Node.js LTS et le package next |
| Accès API | Endpoint Content API activé, avec un token d’authentification en lecture‑seule |
| Déploiement | Vercel, Netlify ou toute plateforme respectant les en‑têtes ISR de Next.js |
Assurez‑vous que le plugin Revisionary est actif afin que chaque modification de contenu soit enregistrée. Le plugin expose l’historique des versions via l’API Content standard, ce qui permet au front‑end de demander la version publiée tout en pouvant interroger les versions passées pour le débogage ou la prévisualisation.
2. Récupérer uniquement la version publiée
Strapi conserve les brouillons, les publications planifiées et les instantanés historiques créés par Revisionary. Pour éviter de servir un brouillon, interrogez l’API avec le filtre publicationState réglé sur live.
// lib/strapi.ts
import fetch from 'node-fetch'
const API_URL = process.env.STRAPI_URL!
const API_TOKEN = process.env.STRAPI_TOKEN!
export async function fetchLive<T>(endpoint: string): Promise<T> {
const url = new URL(`${API_URL}/api/${endpoint}`)
// `publicationState=live` force Strapi à ne renvoyer que les entrées publiées
url.searchParams.append('publicationState', 'live')
const res = await fetch(url.toString(), {
headers: { Authorization: `Bearer ${API_TOKEN}` },
})
if (!res.ok) {
throw new Error(`Strapi request failed: ${res.status}`)
}
const { data } = await res.json()
return data as T
}Toutes les fonctions de récupération de données au niveau des pages doivent utiliser fetchLive afin que la revalidation ISR récupère toujours le même instantané stable que les rédacteurs ont publié.
3. Ajouter l’ISR à un composant de page
Next.js prend en charge l’ISR via la propriété revalidate renvoyée par getStaticProps. Combinez‑la avec la récupération en direct de l’étape précédente.
// pages/[slug].tsx
import { GetStaticPaths, GetStaticProps } from 'next'
import { fetchLive } from '@/lib/strapi'
import type { Article } from '@/types'
export const getStaticPaths: GetStaticPaths = async () => {
const articles = await fetchLive<Article[]>('articles')
const paths = articles.map(a => ({ params: { slug: a.attributes.slug } }))
return { paths, fallback: 'blocking' }
}
export const getStaticProps: GetStaticProps = async ({ params }) => {
const slug = params?.slug as string
const article = await fetchLive<Article>(`articles?filters[slug][$eq]=${slug}`)
if (!article) return { notFound: true }
return {
props: { article },
// Regénérer la page au maximum une fois toutes les 60 secondes
revalidate: 60,
}
}
export default function ArticlePage({ article }: { article: Article }) {
return (
<article>
<h1>{article.attributes.title}</h1>
<div dangerouslySetInnerHTML={{ __html: article.attributes.body }} />
</article>
)
}La ligne revalidate: 60 indique à Next.js de garder la page à jour pendant jusqu’à une minute. Ajustez l’intervalle en fonction du rythme éditorial.
4. Respecter les verrous d’édition et les revues
Revisionary introduit des verrous d’édition pour empêcher deux rédacteurs de s’écraser mutuellement ainsi qu’un espace Review pour le contenu en attente d’approbation. Lorsqu’une entrée est verrouillée ou en revue, vous ne devez pas déclencher de régénération. Laissez le verrou en place jusqu’à ce que le relecteur publie.
4.1 Détecter un verrou via l’API
Revisionary ajoute un drapeau locked et un champ reviewStatus aux métadonnées de l’entrée. Interrogez ces champs en même temps que le contenu habituel.
export async function fetchArticleMeta(slug: string) {
const url = new URL(`${API_URL}/api/articles`)
url.searchParams.append('filters[slug][$eq]', slug)
url.searchParams.append('fields', 'locked,reviewStatus')
url.searchParams.append('publicationState', 'live')
const res = await fetch(url.toString(), {
headers: { Authorization: `Bearer ${API_TOKEN}` },
})
const { data } = await res.json()
return data[0]?.attributes ?? null
}4.2 Revalidation conditionnelle
Next.js vous permet de revalider programmatiquement un chemin depuis une route API. Combinez la vérification du verrou avec un webhook que Strapi déclenche à la publication.
// pages/api/revalidate.ts
import type { NextApiRequest, NextApiResponse } from 'next'
import { fetchArticleMeta } from '@/lib/strapi'
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const token = req.headers['x-webhook-token']
if (token !== process.env.STRAPI_WEBHOOK_TOKEN) {
return res.status(401).end('Invalid token')
}
const { slug } = req.body
const meta = await fetchArticleMeta(slug)
if (meta.locked || meta.reviewStatus !== 'approved') {
// Ignorer la revalidation ; le contenu n’est pas prêt à être consommé publiquement
return res.status(200).json({ revalidated: false, reason: 'locked or pending review' })
}
try {
await res.revalidate(`/${slug}`)
return res.json({ revalidated: true })
} catch (err) {
return res.status(500).json({ error: 'Revalidation failed' })
}
}Configurez Strapi pour appeler cet endpoint lors de l’événement du cycle de vie afterPublish. Le payload du webhook doit contenir le slug de l’entrée publiée.
5. Exploiter l’historique des versions pour la prévisualisation
Même si l’ISR ne sert que la version live, vous pouvez toujours proposer un mode prévisualisation qui lit une révision spécifique. Revisionary stocke chaque version sous une relation versions ; l’API Content peut la récupérer avec une requête populate[versions].
export async function fetchVersion(slug: string, versionId: number) {
const url = new URL(`${API_URL}/api/articles`)
url.searchParams.append('filters[slug][$eq]', slug)
url.searchParams.append('populate[versions][filters][id][$eq]', versionId.toString())
const res = await fetch(url.toString(), {
headers: { Authorization: `Bearer ${API_TOKEN}` },
})
const { data } = await res.json()
return data[0]?.attributes?.versions?.data[0]?.attributes ?? null
}Exposez une route de prévisualisation qui définit previewData (mode preview de Next.js) et rend la version sélectionnée. Cela permet aux rédacteurs de voir exactement ce qui sera publié sans affecter le cache ISR.
6. Intégration CI/CD
- Lint & type‑check – Exécutez
eslintettscà la fois sur le dossier des plugins Strapi et le code Next.js. - Test du schéma – Utilisez un script qui interroge l’endpoint
schemade l’API Content et vérifie que les champs requis existent. - Test du verrou de version – Simulez un verrou d’édition de brouillon via l’API et vérifiez que le webhook de revalidation renvoie
revalidated: false. - Build – Compilez l’application Next.js (
next build). La compilation échouera si ungetStaticPropslève une exception à cause de l’absence de données live. - Déploiement – Déployez sur Vercel (ou votre hôte). Vercel respecte automatiquement les intervalles
revalidate.
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Node
uses: actions/setup-node@v3
with:
node-version: '20'
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- name: Verify Strapi schema
env:
STRAPI_URL: ${{ secrets.STRAPI_URL }}
STRAPI_TOKEN: ${{ secrets.STRAPI_TOKEN }}
run: node scripts/verify-schema.js
- run: npm run build7. Observabilité et journalisation
Revisionary consigne chaque verrou, restauration et action de suppression dans le audit log. Transférez ces journaux vers votre plateforme d’observabilité préférée (par ex., Loki, Datadog) en activant le transport logger de Strapi. Combinez le journal d’audit avec les journaux de requêtes Next.js pour tracer le chemin complet depuis l’action d’un rédacteur jusqu’au moment où la page est régénérée.
// config/logger.js (Strapi)
module.exports = ({ env }) => ({
transports: [
{
level: 'info',
transport: {
target: 'pino-pretty',
options: { destination: './logs/combined.log' },
},
},
// Ajouter un transport HTTP pour envoyer les journaux vers un système externe
{
level: 'warn',
transport: {
target: 'pino-http',
options: { url: env('LOGGING_ENDPOINT') },
},
},
],
})Surveiller les champs locked et reviewStatus en temps réel vous aide à détecter quand un gel de contenu empêche involontairement les mises à jour ISR.
8. Rassembler le tout
- Configurer Revisionary – Choisissez quels types de contenu sont versionnés via la page Choose what is versioned.
- Exposer l’API live – Utilisez le filtre
publicationState=livedans chaque récupération Next.js. - Configurer le webhook – Dirigez le webhook afterPublish de Strapi vers
/api/revalidate. - Implémenter la vérification du verrou – Le gestionnaire de revalidation ignore les pages encore en cours d’édition ou de révision.
- Ajouter la prévisualisation – Utilisez le endpoint d’historique des versions pour rendre les brouillons aux relecteurs internes.
- Automatiser CI/CD – Exécutez lint, type‑check, vérification du schéma et étapes de build à chaque commit.
- Journaliser et observer – Transférez les logs d’audit Revisionary et les logs de requêtes Next.js vers un outil d’observabilité central.
Suivre ces étapes vous fournit un front‑end rapide et cache‑friendly qui ne fuit jamais les brouillons non publiés, tout en préservant la traçabilité éditoriale complète offerte par Revisionary.
9. Pièges courants
| Symptôme | Cause probable | Solution |
|---|---|---|
| L’ISR sert du contenu périmé après une publication | Incohérence du token du webhook | Assurez‑vous que STRAPI_WEBHOOK_TOKEN correspond au token configuré dans Strapi |
| 404 sur l’URL de prévisualisation | Drapeau previewData manquant | Appelez res.setPreviewData({}) avant de rendre la page de prévisualisation |
| Journal d’audit vide pour les verrous | Revisionary non activé sur le type de contenu | Activez le versionnage pour le type dans Choose what is versioned |
| Boucle de revalidation (reconstructions continues) | publicationState omis, récupération des brouillons | Ajoutez toujours publicationState=live aux appels API |
10. Conclusion
L’Incremental Static Regeneration vous offre les performances des pages statiques tout en gardant votre site à jour. Lorsque la source de données est un CMS headless sensible aux versions comme Strapi, l’étape supplémentaire consiste à respecter les états éditoriaux — brouillons, verrous et revues — afin que seul le contenu approuvé atteigne le cache. En filtrant sur l’état de publication live, en vérifiant les métadonnées de verrou de Revisionary et en connectant un webhook qui déclenche conditionnellement la revalidation, vous obtenez un workflow sûr, observable et prêt pour le CI. La même API Content vous permet également de créer des expériences de prévisualisation qui récupèrent n’importe quelle version historique, offrant aux rédacteurs la confiance que leurs modifications sont correctement capturées avant la mise en ligne.
L’équipe Revisionary
Les développeurs de Revisionary, un plugin d’historique des versions pour Strapi 5. Nous écrivons sur Strapi, le développement web, l’IA et la sécurité des applications.
Cet article a été rédigé avec l'aide d'une IA.