vendredi 25 septembre 2026

À propos

Contact

Headless & API

Un build Next.js qui dépasse une heure : paginer la récupération WPGraphQL

Un site de cinq mille articles faisait exploser le temps de build en régénération statique complète. Recette pour ne reconstruire que les pages modifiées plutôt que tout régénérer.

Par Clément Hadrot • 11 janvier 2023 • 4 min de lecture • Aucun commentaire
Un build Next.js qui dépasse une heure : paginer la récupération WPGraphQL

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.

L'essentiel à retenir : Une requête unique sur 5000 articles saturait la mémoire du build ; La pagination par curseur a ramené le temps sous les dix minutes ; generateStaticParams n'a plus besoin de tout précalculer

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.

Partager :

À propos de l'auteur

Clément Hadrot

Développeur WordPress, passionné par Elementor, le FSE et l’automatisation par IA.

Voir tous ses articles

Dans la même veine

À lire aussi