Aller au contenu

API GraphQL sécurisée par rôles avec Strapi 5 & Next.js 15 : aperçu ISR

Guide pour installer le plugin GraphQL de Strapi 5, configurer accès par rôle, récupérer données dans Next.js 15, et activer aperçus, l’ISR et revalidation à la demande.

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

turned-on grey laptop computer
Photo de Arnold Francisca sur Unsplash

Utilisez le plugin GraphQL de Strapi 5 avec des permissions basées sur les rôles, récupérez les données dans Next.js 15 via Apollo Client, et activez les aperçus éditeur, l’Incremental Static Regeneration (ISR) et la revalidation à la demande.

1. Installer et activer le plugin GraphQL dans Strapi 5

bash
# Utilisation de npm
npm install @strapi/plugin-graphql
# ou Yarn
yarn add @strapi/plugin-graphql

Après l’installation, redémarrez Strapi. Le plugin apparaît sous Plugins → GraphQL dans l’interface d’administration. Aucune étape supplémentaire n’est nécessaire pour l’activer.

a long hallway with glass doors leading to another room
Photo de Paul Hanaoka sur Unsplash

2. Sécuriser le point d’accès GraphQL

2.1 Configuration de base du plugin

Créez ou modifiez config/plugins.js (ou config/plugins.ts si vous utilisez TypeScript) et ajoutez les options GraphQL. Les paramètres de sécurité les plus importants sont depthLimit, maxLimit, et la désactivation du Playground en production.

js
module.exports = ({ env }) => ({
  graphql: {
    config: {
      endpoint: "/graphql",
      shadowCRUD: true,
      depthLimit: 7,            // empêcher les requêtes trop profondes
      defaultLimit: 25,
      maxLimit: 100,
      apolloServer: {
        tracing: false,
        // Désactiver le GraphQL Playground / Sandbox en production
        introspection: env("NODE_ENV") !== "production",
        landingPage: false,
      },
    },
  },
});

Pour la liste complète des options, consultez la documentation Strapi sur la configuration GraphQL【1†L87-L99】.

2.2 Permissions basées sur les rôles

Le plugin Users & Permissions de Strapi contrôle quels rôles peuvent exécuter quelles opérations GraphQL. Ouvrez Settings → Roles dans le panneau d’administration, sélectionnez un rôle et activez les permissions GraphQL que vous souhaitez exposer, par ex.

  • plugin::users-permissions.user.find – lire les utilisateurs
  • api::article.article.find – lister les articles publics
  • api::article.article.create – permettre aux éditeurs de créer des articles
js
// Exemple de resolversConfig dans config/plugins.js
resolversConfig: {
  "Query.articles": { auth: { scope: ["api::article.article.find"] } },
  "Mutation.createArticle": { auth: { scope: ["api::article.article.create"] } },
},

Pour approfondir, consultez la documentation sur les politiques avancées GraphQL【1†L480-L490】.

3. Authentifier les requêtes depuis Next.js

Strapi utilise des jetons JWT émis par le plugin Users & Permissions. Pour obtenir un jeton, appelez la mutation de connexion :

graphql
mutation Login($input: UsersPermissionsLoginInput!) {
  login(input: $input) {
    jwt
    user { id username }
  }
}

Dans Next.js, vous pouvez stocker le JWT dans un cookie HttpOnly ou dans le stockage local (uniquement pour les récupérations côté client). Chaque requête GraphQL doit inclure l’en‑tête :

code
Authorization: Bearer <jwt>

Strapi valide automatiquement le jeton et applique les permissions de rôle définies précédemment【1†L218-L229】.

4. Configurer Apollo Client dans un projet Next.js 15

4.1 Installer les dépendances

bash
npm install @apollo/client graphql
# aides TypeScript optionnelles
npm install -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations

4.2 Créer une instance Apollo client

Créez lib/apollo.ts :

ts
import { ApolloClient, InMemoryCache, HttpLink } from "@apollo/client";
import fetch from "cross-fetch";

export const createApolloClient = (token?: string) => {
  return new ApolloClient({
    ssrMode: typeof window === "undefined",
    link: new HttpLink({
      uri: process.env.NEXT_PUBLIC_STRAPI_GRAPHQL_ENDPOINT, // e.g. http://localhost:1337/graphql
      fetch,
      headers: token ? { Authorization: `Bearer ${token}` } : {},
    }),
    cache: new InMemoryCache({
      // Utiliser une clé de cache stable pour les documents Strapi
      dataIdFromObject: (o: any) => o.documentId ? `${o.__typename}:${o.documentId}` : null,
    }),
  });
};

La fonction dataIdFromObject reflète le champ documentId de Strapi, garantissant la normalisation du cache【1†L687-L692】.

4.3 Requêtes typées avec génération de code (optionnel)

Ajoutez un codegen.yml :

yaml
schema: "${process.env.NEXT_PUBLIC_STRAPI_GRAPHQL_ENDPOINT}"
documents: "src/**/*.tsx"
generates:
  src/generated/graphql.tsx:
    plugins:
      - "typescript"
      - "typescript-operations"
      - "typescript-react-apollo"

Exécutez npx graphql-codegen pour générer des hooks typés comme useArticlesQuery.

5. Mettre en œuvre l’aperçu éditeur avec la fonctionnalité de prévisualisation de Strapi

Strapi 5 fournit un système de prévisualisation intégré qui peut générer une URL signée pour une entrée en brouillon. Ajoutez ce qui suit à config/admin.ts :

ts
export default ({ env }) => ({
  preview: {
    enabled: true,
    config: {
      allowedOrigins: [env("CLIENT_URL")],
      async handler(uid, { documentId, locale, status }) {
        const doc = await strapi.documents(uid).findOne({ documentId });
        const { slug } = doc;
        const params = new URLSearchParams({
          secret: env("PREVIEW_SECRET"),
          slug,
          uid,
          status,
        });
        return `${env("CLIENT_URL")}/api/preview?${params}`;
      },
    },
  },
});

Définissez les variables d’environnement dans .env :

code
CLIENT_URL=http://localhost:3000
PREVIEW_SECRET=super‑secret-key

5.1 Route de prévisualisation Next.js

Créez app/api/preview/route.ts (app router de Next.js 15) :

ts
import { NextResponse } from "next/server";

export async function GET(request: Request) {
  const url = new URL(request.url);
  const secret = url.searchParams.get("secret");
  const slug = url.searchParams.get("slug");

  if (secret !== process.env.PREVIEW_SECRET) {
    return new NextResponse("Invalid secret", { status: 401 });
  }

  // Activer le mode brouillon pour cette requête
  const draftResponse = NextResponse.next();
  draftResponse.headers.set("Cache-Control", "no-store");
  draftResponse.cookies.set("nextjs-draft-mode", "1", { httpOnly: true, path: "/" });

  // Rediriger vers la page de l’article
  return NextResponse.redirect(`${process.env.CLIENT_URL}/${slug}`);
}

La route valide le secret, active le draft mode, puis redirige l’éditeur vers l’URL du front‑end.

5.2 Consommer le drapeau de prévisualisation dans les pages

Dans un composant de page (par ex. app/articles/[slug]/page.tsx) :

tsx
import { gql } from "@apollo/client";
import { createApolloClient } from "@/lib/apollo";

export async function generateStaticParams() {
  const client = createApolloClient();
  const { data } = await client.query({
    query: gql`query AllSlugs { articles(filters: { status: { eq: "published" } }) { data { attributes { slug } } } }`,
  });
  return data.articles.data.map((a: any) => ({ slug: a.attributes.slug }));
}

export default async function ArticlePage({ params }: { params: { slug: string } }) {
  const { draftMode } = await import("next/headers");
  const isDraft = draftMode().isEnabled;

  const client = createApolloClient();
  const { data } = await client.query({
    query: gql`query Article($slug: String!, $status: String!) {
      articles(filters: { slug: { eq: $slug }, status: { eq: $status } }) {
        data { attributes { title content } }
      }
    }`,
    variables: { slug: params.slug, status: isDraft ? "draft" : "published" },
  });

  const article = data.articles.data[0].attributes;
  return (
    <article>
      <h1>{article.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: article.content }} />
    </article>
  );
}

Lorsque l’éditeur suit l’URL de prévisualisation, draftMode() renvoie true, ce qui fait que la requête GraphQL demande la version draft de l’entrée.

6. Regeneration statique incrémentale (ISR) pour les pages publiques

Next.js 15 prend toujours en charge l’ISR du répertoire pages classique. Dans pages/articles/[slug].tsx :

tsx
import { GetStaticPaths, GetStaticProps } from "next";
import { gql } from "@apollo/client";
import { createApolloClient } from "../lib/apollo";

export const getStaticPaths: GetStaticPaths = async () => {
  const client = createApolloClient();
  const { data } = await client.query({
    query: gql`query Slugs { articles(filters: { status: { eq: "published" } }) { data { attributes { slug } } } }`,
  });
  const paths = data.articles.data.map((a: any) => ({ params: { slug: a.attributes.slug } }));
  return { paths, fallback: "blocking" };
};

export const getStaticProps: GetStaticProps = async ({ params }) => {
  const client = createApolloClient();
  const { data } = await client.query({
    query: gql`query Article($slug: String!) { articles(filters: { slug: { eq: $slug }, status: { eq: "published" } }) { data { attributes { title content } } } }`,
    variables: { slug: params?.slug },
  });
  return {
    props: { article: data.articles.data[0].attributes },
    revalidate: 60, // ISR : rafraîchit chaque minute
  };
};

export default function Article({ article }: { article: { title: string; content: string } }) {
  return (
    <article>
      <h1>{article.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: article.content }} />
    </article>
  );
}

Le champ revalidate indique à Vercel (ou toute plateforme compatible) de reconstruire la page après le nombre de secondes indiqué【7†L44-L53】.

7. Revalidation à la demande après un événement de publication

Lorsqu’un éditeur publie un brouillon, vous voulez purger le cache ISR immédiatement. Strapi peut déclencher un webhook après un événement de cycle de vie publish. Configurez le webhook pour appeler une route API Next.js qui exécute res.revalidate.

7.1 Configuration du webhook Strapi

Dans l’interface admin de Strapi, accédez à Settings → Webhooks → Create webhook :

  • URL : https://your‑site.com/api/revalidate
  • Events : Entry publish pour le type de contenu article

7.2 Point de terminaison de revalidation Next.js

Créez pages/api/revalidate.ts :

ts
import type { NextApiRequest, NextApiResponse } from "next";

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method !== "POST") return res.status(405).end();
  const token = req.headers.authorization?.split(" ")[1];
  if (token !== process.env.REVALIDATE_SECRET) {
    return res.status(401).json({ message: "Invalid secret" });
  }
  const { slug } = req.body;
  try {
    await res.revalidate(`/articles/${slug}`);
    return res.json({ revalidated: true, path: `/articles/${slug}` });
  } catch (err) {
    return res.status(500).json({ message: "Revalidation error", error: err });
  }
}

Définissez REVALIDATE_SECRET dans Vercel (ou votre environnement d’hébergement). Le point de terminaison reçoit le slug du payload du webhook Strapi et force la génération d’une nouvelle page【7†L152-L170】.

8. Optionnel : Utiliser Revisionary pour des aperçus versionnés

Si votre projet utilise déjà le plugin Revisionary, vous bénéficiez d’un journal d’audit complet, d’une corbeille et d’un historique champ‑par‑champ pour chaque brouillon. Le plugin expose également une content API qui renvoie l’historique des versions, que vous pouvez afficher dans une UI de prévisualisation personnalisée. Consultez les pages de documentation de Revisionary telles que /docs/reference/content‑api pour la structure exacte du payload.

9. Tests et considérations CI/CD

  1. Tester les resolvers – utilisez jest avec supertest contre le point d’accès GraphQL, en simulant les en‑têtes JWT.
  2. Flux de prévisualisation E2E – lancez un conteneur Strapi temporaire, créez un brouillon, appelez le webhook de prévisualisation et vérifiez que draftMode() est activé.
  3. Pipeline CI – exécutez npm run build && npm run start dans une étape Docker, puis lancez des tests Cypress qui ciblent à la fois les pages ISR publiques et la route de prévisualisation.
  4. Analyse statique – activez les règles ESLint pour la profondeur/compexité GraphQL et pour l’hygiène du cache Apollo.

10. Observabilité et renforcement de la sécurité

  • Limites de profondeur et de complexité (depthLimit, maxLimit) protègent contre les attaques DoS basées sur les requêtes【1†L88-L96】.
  • Désactiver l’introspection en production (introspection: false) pour masquer le schéma aux utilisateurs non authentifiés【1†L123-L128】.
  • Journal d’audit – Revisionary enregistre chaque publication, restauration et modification de permission, offrant une traçabilité pour les revues de sécurité.
  • Limitation du débit – placez un reverse‑proxy (ex. : Nginx) devant Strapi et limitez les requêtes par IP.
  • HTTPS – imposez TLS au niveau du load balancer et définissez secure: true sur les cookies utilisés pour le mode brouillon.
A network of interconnected cubes and thin blue lines against a light blue background
Photo de GuerrillaBuzz sur Unsplash

11. Résumé

En installant le plugin GraphQL, en configurant les permissions basées sur les rôles et en intégrant Apollo Client dans Next.js 15, vous obtenez une couche de données typée et performante. La fonctionnalité de prévisualisation de Strapi, combinée au mode brouillon de Next.js, permet aux éditeurs de voir instantanément les changements non publiés. L’ISR maintient les pages publiques rapides, tandis que la revalidation à la demande garantit qu’un article nouvellement publié est servi sans délai. L’ajout de Revisionary apporte la versionnage complet du contenu, un journal d’audit et des capacités de restauration – indispensables pour un CMS headless en production.

Mots-clésStrapiGraphQLNext.jsApolloTypeScriptISR

Sources

  1. Strapi GraphQL plugin documentation
  2. Strapi Users & Permissions GraphQL API
  3. Strapi preview feature guide
  4. Next.js preview mode documentation
  5. Next.js incremental static regeneration docs
  6. Apollo Client integration with Next.js

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