Symptôme
Un site d’actualités locales, plus de cinq mille articles publiés en huit ans d’existence, venait d’être migré vers un front Next.js consommant WordPress via WPGraphQL. Le build de production, exécuté à chaque déploiement, prenait plus d’une heure, un délai devenu incompatible avec le rythme de publication du client, qui souhaitait pouvoir republier plusieurs fois par jour sans attendre un cycle de build aussi long à chaque fois.
Diagnostic
La fonction generateStaticParams du front récupérait la totalité des cinq mille articles en une seule requête WPGraphQL, avec une limite de pagination fixée arbitrairement haut :
export async function generateStaticParams() {
const { data } = await client.query({
query: gql`
query TousLesArticles {
posts(first: 5000) {
nodes { slug }
}
}
`,
})
return data.posts.nodes.map((p) => ({ slug: p.slug }))
}
Cette requête unique surchargeait à la fois WordPress, qui devait résoudre cinq mille nœuds en une seule réponse, souvent au bord du délai d’exécution PHP maximal configuré sur l’hébergement, et le processus de build Next.js lui-même, dont la consommation mémoire grimpait fortement le temps de traiter cette réponse volumineuse avant de lancer la génération des pages individuelles.

Correctif : paginer la récupération par curseur
La première partie du correctif a consisté à remplacer la requête unique par une pagination par curseur, en utilisant le champ pageInfo exposé nativement par WPGraphQL :
async function recupererTousLesSlugs() {
let curseur = null
let tousLesSlugs = []
let aEncore = true
while (aEncore) {
const { data } = await client.query({
query: gql`
query Articles($after: String) {
posts(first: 100, after: $after) {
nodes { slug }
pageInfo {
hasNextPage
endCursor
}
}
}
`,
variables: { after: curseur },
})
tousLesSlugs = tousLesSlugs.concat(data.posts.nodes)
aEncore = data.posts.pageInfo.hasNextPage
curseur = data.posts.pageInfo.endCursor
}
return tousLesSlugs
}
export async function generateStaticParams() {
const slugs = await recupererTousLesSlugs()
return slugs.map((p) => ({ slug: p.slug }))
}
Cette pagination par lots de cent articles a déjà réduit sensiblement la pression mémoire côté WordPress, en évitant qu’une seule requête ne tente de résoudre cinq mille nœuds d’un coup. Le temps de build est passé d’un peu plus d’une heure à environ vingt-cinq minutes avec cette seule modification.
Le vrai changement : ne plus tout régénérer à chaque build
Vingt-cinq minutes restaient encore trop longues pour le rythme de publication souhaité. Le second correctif, plus structurant, a consisté à abandonner la génération statique complète à chaque déploiement pour ne précalculer, au build, qu’un sous-ensemble restreint des articles les plus récents et les plus consultés, en laissant Next.js générer les autres pages à la demande grâce à l’ISR (Incremental Static Regeneration), avec l’option dynamicParams: true :
export const dynamicParams = true
export async function generateStaticParams() {
// On ne précalcule que les 200 articles les plus récents,
// le reste sera généré à la demande puis mis en cache.
const slugsRecents = await recupererSlugsRecents(200)
return slugsRecents.map((p) => ({ slug: p.slug }))
}
export const revalidate = 3600
Ce mécanisme d’ISR et la revalidation par webhook, qui déclenche une régénération ciblée dès qu’un article est modifié, sont traités en détail dans un article distinct de ce blog ; ce billet se concentre volontairement sur le problème de pagination et de volume qui précède cette optimisation.
Prévention
- Toute requête de listage dépassant quelques centaines d’éléments est désormais systématiquement paginée par curseur, jamais récupérée en un seul appel, quel que soit le volume attendu au départ du projet.
- Le temps de build est surveillé à chaque déploiement, avec une alerte si un build dépasse une durée seuil définie avec le client, pour détecter une dérive avant qu’elle ne devienne critique.
- Le nombre d’articles précalculés au build reste un paramètre ajustable, revu périodiquement selon les statistiques réelles de consultation du site.
Reconstruire l’intégralité d’un site à chaque publication n’a de sens que tant que le site reste petit : passé un certain volume, ce n’est plus une question de performance, c’est une question de viabilité du processus de publication lui-même.
En résumé
Le temps de build est passé de soixante-huit minutes à neuf minutes en combinant une pagination par curseur, pour ne plus saturer une seule requête GraphQL, et une génération statique limitée aux contenus les plus récents, le reste étant produit à la demande. Sur un site en croissance continue, ce sont deux réflexes à adopter tôt, bien avant que le volume de contenu ne rende le problème douloureux.