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
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
# Utilisation de npm
npm install @strapi/plugin-graphql
# ou Yarn
yarn add @strapi/plugin-graphqlAprè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.
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.
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 utilisateursapi::article.article.find– lister les articles publicsapi::article.article.create– permettre aux éditeurs de créer des articles
// 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 :
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 :
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
npm install @apollo/client graphql
# aides TypeScript optionnelles
npm install -D @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations4.2 Créer une instance Apollo client
Créez lib/apollo.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 :
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 :
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 :
CLIENT_URL=http://localhost:3000
PREVIEW_SECRET=super‑secret-key5.1 Route de prévisualisation Next.js
Créez app/api/preview/route.ts (app router de Next.js 15) :
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) :
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 :
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 publishpour le type de contenuarticle
7.2 Point de terminaison de revalidation Next.js
Créez pages/api/revalidate.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
- Tester les resolvers – utilisez
jestavecsupertestcontre le point d’accès GraphQL, en simulant les en‑têtes JWT. - 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é. - Pipeline CI – exécutez
npm run build && npm run startdans une étape Docker, puis lancez des tests Cypress qui ciblent à la fois les pages ISR publiques et la route de prévisualisation. - 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: truesur les cookies utilisés pour le mode brouillon.
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.
Sources
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.