Aller au contenu

Comment annuler le contenu dans Strapi 5 pour un frontend Next.js 15

Apprenez à activer l’historique du contenu dans Strapi 5, à récupérer les versions via GraphQL ou REST, et à implémenter un rollback fiable dans une appli Next.js 15, CI/CD.

Par l’équipe Revisionary · · 5 min de lecture

turned-on monitor
Photo de Stephen Phillips - Hostreviews.co.uk sur Unsplash

Strapi 5 peut stocker chaque modification que vous effectuez sur des types de contenu versionnés, et Next.js 15 peut re‑valider les pages statiques lorsqu’un brouillon est publié. En combinant l’Historique du contenu de Strapi (fourni par le plugin Revisionary) avec l’API Document Service, GraphQL ou l’API REST, vous pouvez bâtir un mécanisme de rollback fiable qui fonctionne depuis l’interface d’administration jusqu’au code front‑end.

1. Prérequis et fonctionnement de la fonctionnalité

  • Strapi 5 installé sur Node 18‑24 avec une base de données prise en charge (PostgreSQL, MySQL ou SQLite). 
  • Le plugin Revisionary actif (plan Growth ou Enterprise) – il enregistre les différences au niveau des champs, propose une chronologie des versions et conserve une corbeille pour les entrées supprimées. 
  • Un projet Next.js 15 écrit en React et TypeScript. 
  • Connaissance de base du workflow Draft & Publish de Strapi.

Lorsque ces conditions sont réunies, chaque modification effectuée via le panneau d’administration crée une nouvelle version qui peut être listée, comparée et restaurée. Le front‑end peut interroger la version publiée actuelle, un brouillon spécifique ou n’importe quel instantané historique, rendant le rollback possible en quelques appels d’API.

2. Activer Draft & Publish et l’historique du contenu

  1. Activer Draft & Publish pour les types de contenu que vous souhaitez versionner (par ex. article). Dans le Content‑Type Builder de Strapi, activez Draft & Publish.
  2. Installer Revisionary depuis le Marketplace, puis lancez l’installeur comme décrit dans le guide /docs/getting-started/install. Le plugin ajoute un panneau Version history à droite du Content Manager.
  3. Configurer la rétention si vous avez besoin d’une fenêtre plus courte que la valeur par défaut (Growth = 14 jours, Enterprise = 30 jours). Ajoutez dans /config/admin.js :
js
module.exports = ({ env }) => ({
  history: { retentionDays: 7 }, // conserver les versions pendant une semaine
});

Ce paramètre ne peut pas être étendu au‑delà des limites du plan, mais le réduire aide à maîtriser l’espace de stockage.

3. Lister les versions – les points d’accès API

3.1 Point d’accès Content‑Manager (admin uniquement)

L’UI admin appelle un point d’accès interne qui peut aussi être utilisé depuis un script côté serveur :

code
GET /content-manager/history-versions?contentType=api::article.article&documentId=abc123

Il renvoie un tableau paginé d’objets version contenant id, createdAt, author et un instantané data des champs à ce moment‑là.

3.2 Document Service API – requêtes conscientes des versions

Le Document Service de Strapi vous permet de récupérer une version précise en passant le paramètre status :

ts
// Récupérer la version publiée
const published = await strapi.documents('api::article.article').findOne({
  documentId: 'abc123',
  status: 'published',
});
// Récupérer le brouillon (les dernières modifications non publiées)
const draft = await strapi.documents('api::article.article').findOne({
  documentId: 'abc123',
  status: 'draft',
});

L’énumération status est documentée sur la page /docs/reference/content-api.

3.3 GraphQL – status & publicationFilter

Lorsque vous utilisez le plugin GraphQL, vous pouvez demander une version précise directement dans la requête :

graphql
query GetArticle($id: ID!, $status: PublicationStatus!) {
  article(id: $id, status: $status) {
    title
    content
    updatedAt
  }
}

Les valeurs supportées de PublicationStatus sont DRAFT et PUBLISHED. Vous pouvez également filtrer selon la relation entre brouillon et version publiée avec publicationFilter, par ex. MODIFIED pour obtenir les brouillons contenant des changements non publiés.

3.4 REST – gestion explicite des brouillons

Pour les routes REST classiques, ajoutez ?status=draft pour garder le document en mode brouillon ; omettre le paramètre publie immédiatement.

bash
PUT /api/articles/123?status=draft   # conserve comme brouillon
PUT /api/articles/123                # publie instantanément

Voir l’extrait officiel sur /docs/reference/content-api.

4. Implémenter le rollback dans un front‑end Next.js 15

4.1 Vue d’ensemble de l’architecture

  1. Couche API – une route API Next.js fine (/pages/api/rollback.ts) qui communique avec Strapi via GraphQL ou les points d’accès REST du Document Service.
  2. Côté client – un composant React dans le tableau de bord admin qui liste les versions disponibles et déclenche l’API de rollback.
  3. Revalidation – après un rollback réussi, la route appelle res.revalidate('/articles/[slug]') afin que les pages ISR soient régénérées.

4.2 Helper TypeScript pour récupérer les versions

Créez un module utilitaire lib/strapi.ts :

ts
import fetch from 'node-fetch';

const STRAPI_URL = process.env.STRAPI_URL!;
const TOKEN = process.env.STRAPI_TOKEN!;

export async function fetchVersions(documentId: string) {
  const res = await fetch(
    `${STRAPI_URL}/content-manager/history-versions?contentType=api::article.article&documentId=${documentId}`,
    {
      headers: { Authorization: `Bearer ${TOKEN}` },
    }
  );
  if (!res.ok) throw new Error('Failed to load versions');
  return res.json(); // { data: Version[], meta: {...} }
}

export async function restoreVersion(documentId: string, versionId: string) {
  const res = await fetch(
    `${STRAPI_URL}/content-manager/restore-version`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${TOKEN}`,
      },
      body: JSON.stringify({ documentId, versionId }),
    }
  );
  if (!res.ok) throw new Error('Rollback failed');
  return res.json();
}

Le point d’accès restore-version fait partie de l’API interne de Revisionary ; il copie l’instantané sélectionné dans le brouillon actuel et marque l’entrée comme Modified.

4.3 Route API qui déclenche le rollback et la revalidation

Fichier : pages/api/articles/[id]/rollback.ts

ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { restoreVersion } from '@/lib/strapi';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method !== 'POST') return res.status(405).end();
  const { id } = req.query as { id: string };
  const { versionId } = req.body as { versionId: string };

  try {
    await restoreVersion(id, versionId);
    // Re‑valider la page ISR affichant l’article
    await res.revalidate(`/articles/${id}`);
    return res.status(200).json({ message: 'Rollback successful and page revalidated' });
  } catch (e) {
    console.error(e);
    return res.status(500).json({ error: 'Rollback failed' });
  }
}

res.revalidate de Next.js 15 fonctionne sur Vercel, Netlify Edge ou toute plateforme supportant la revalidation à la demande.

4.4 Composant UI côté front‑end

tsx
import { useEffect, useState } from 'react';
import { fetchVersions, restoreVersion } from '@/lib/strapi';

interface Version { id: string; createdAt: string; data: Record<string, any>; }

export default function VersionRollback({ articleId }: { articleId: string }) {
  const [versions, setVersions] = useState<Version[]>([]);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    fetchVersions(articleId).then((res) => setVersions(res.data));
  }, [articleId]);

  const handleRollback = async (versionId: string) => {
    setLoading(true);
    await fetch('/api/articles/' + articleId + '/rollback', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ versionId }),
    });
    setLoading(false);
    // Rafraîchir éventuellement l’UI après le rollback
    window.location.reload();
  };

  return (
    <section>
      <h2>Historique des versions</h2>
      {loading && <p>Rollback en cours…</p>}
      <ul>
        {versions.map((v) => (
          <li key={v.id}>
            {new Date(v.createdAt).toLocaleString()} –
            <button onClick={() => handleRollback(v.id)} disabled={loading}>
              Revenir à cette version
            </button>
          </li>
        ))}
      </ul>
    </section>
  );
}

Le composant récupère la liste des versions via le helper, affiche les horodatages et appelle la route API pour effectuer la restauration.

Computer screen displaying HTML code for a web development project
Photo de Mohammad Rahmani sur Unsplash

5. Rollback uniquement GraphQL (optionnel)

Si votre équipe préfère un flux purement GraphQL, vous pouvez exposer une mutation personnalisée qui appelle en interne le service Revisionary. Ajoutez dans ./src/api/article/services/article.js :

js
module.exports = {
  async rollback(ctx) {
    const { documentId, versionId } = ctx.request.body;
    await strapi.service('plugin::revisionary.version').restore({ documentId, versionId });
    return { ok: true };
  },
};

Puis enregistrez la mutation dans ./src/api/article/config/schema.graphql :

graphql
extend type Mutation {
  rollbackArticle(documentId: ID!, versionId: ID!): RollbackResponse!
}

type RollbackResponse { ok: Boolean! }

Les clients peuvent désormais exécuter :

graphql
mutation Rollback($doc: ID!, $ver: ID!) {
  rollbackArticle(documentId: $doc, versionId: $ver) {
    ok
  }
}

Après le succès de la mutation, déclenchez une requête de revalidation depuis le front‑end (par ex. fetch('/api/revalidate?slug=article-slug')).

6. Tester le flux de rollback

6.1 Tests unitaires de la route API

ts
import handler from '@/pages/api/articles/[id]/rollback.ts';
import { createMocks } from 'node-mocks-http';

test('POST déclenche restore et revalidation', async () => {
  const { req, res } = createMocks({
    method: 'POST',
    url: '/api/articles/123/rollback',
    body: { versionId: 'v456' },
  });
  await handler(req, res);
  expect(res._getStatusCode()).toBe(200);
  const data = JSON.parse(res._getData());
  expect(data.message).toContain('successful');
});

Le test vérifie que la route renvoie 200 et que la forme du JSON correspond aux attentes. Vous pouvez moquer restoreVersion et res.revalidate avec des espions Jest.

6.2 Test d’intégration avec le Document Service

ts
import { Strapi } from '@strapi/strapi';

test('restore crée un nouveau brouillon identique à la version choisie', async () => {
  const strapi = await Strapi().load();
  const articleId = 'abc123';
  const versions = await strapi.documents('api::article.article').findMany({
    documentId: articleId,
    populate: '*',
    status: 'draft',
  });
  const target = versions[0];
  // Simuler un rollback
  await strapi.service('plugin::revisionary.version').restore({ documentId: articleId, versionId: target.id });
  const after = await strapi.documents('api::article.article').findOne({ documentId: articleId, status: 'draft' });
  expect(after.title).toBe(target.data.title);
});

Le test d’intégration s’exécute contre une instance Strapi temporaire (démarrée en CI) et valide que le brouillon restauré correspond à l’instantané.

7. Considérations CI/CD

  1. Exécuter les tests end‑to‑end dans GitHub Actions avant le déploiement. Exemple :
yaml
name: CI
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_USER: strapi
          POSTGRES_PASSWORD: strapi
          POSTGRES_DB: strapi
        ports: ['5432:5432']
    steps:
      - uses: actions/checkout@v4
      - name: Install deps
        run: yarn install --frozen-lockfile
      - name: Build Strapi
        run: yarn build:api
      - name: Start Strapi
        run: yarn start &
        env:
          DATABASE_CLIENT: postgres
          DATABASE_HOST: localhost
          DATABASE_PORT: 5432
          DATABASE_NAME: strapi
          DATABASE_USERNAME: strapi
          DATABASE_PASSWORD: strapi
      - name: Run tests
        run: yarn test
  1. Déployer Next.js sur Vercel (ou toute plateforme) et ajouter un webhook dans Strapi qui appelle le point d’accès /api/revalidate de Vercel après une publication ou un rollback réussi. La charge utile du webhook peut être filtrée pour ne s’activer que sur le type de contenu article.
  2. Cache sensible aux versions – définissez Cache-Control: stale-while-revalidate sur les requêtes GraphQL afin que les navigateurs conservent la page publiée précédente pendant la régénération.

8. Observabilité et journal d’audit

Lorsque le plugin audit‑logs (Enterprise) est activé, Strapi enregistre chaque restauration de version, chaque publication et chaque suppression. Vous pouvez transmettre ces logs vers un système externe (ELK, Datadog) via un middleware personnalisé :

js
module.exports = () => async (ctx, next) => {
  await next();
  const { method, url, state } = ctx;
  if (method === 'POST' && url.includes('/restore-version')) {
    console.log(`[audit] ${state.user?.email} restored version ${ctx.request.body.versionId}`);
  }
};

Ces traces vous aident à répondre aux exigences de conformité et à repérer rapidement les rollbacks inattendus.

9. Récapitulatif

  1. Activer Draft & Publish et installer Revisionary.
  2. Configurer la rétention selon votre politique de conservation des données.
  3. Utiliser l’API Document Service (ou GraphQL) pour récupérer la version publiée actuelle et les brouillons nécessaires.
  4. Construire une route API Next.js qui appelle le point d’accès restore-version de Revisionary, puis déclencher la revalidation ISR.
  5. Ajouter un composant React dans votre UI éditoriale pour lister les versions et lancer la requête de rollback.
  6. Rédiger tests unitaires et d’intégration qui mockent le client Strapi et vérifient l’état du brouillon après restauration.
  7. Intégrer le flux dans votre pipeline CI/CD et configurer la surveillance via les audit‑logs pour une visibilité en production.

En suivant ces étapes, vous obtenez un mécanisme de rollback prêt pour la production, compatible avec REST, GraphQL et le Document Service, tout en maintenant le front‑end synchronisé grâce à l’ISR de Next.js. Le même modèle peut être étendu à d’autres types de contenu : il suffit d’activer la version dans les réglages de Revisionary.

Mots-clésStrapi 5Next.js 15Versionnage de contenuRestaurationTypeScriptGraphQL

Sources

  1. Content History feature – Strapi docs
  2. Document Service status API – Strapi docs
  3. GraphQL API reference – Strapi docs
  4. REST API status snippet – Strapi docs
  5. Draft & Publish feature – Strapi docs

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.

Tous les articles