Un client, 800 articles, 4,7 secondes de chargement. En février 2026, un éditeur de contenu m’a contacté avec un site WordPress agonisant : un thème de 2019, douze plugins dont huit abandonnés, un score Lighthouse de 34/100 et un LCP de 6,3 secondes. Google le rétrogradait méthodiquement dans les résultats, et le wp-admin encaissait plus de 3000 tentatives de brute force par jour. La réponse a été une migration vers le WordPress headless avec Next.js 15. Ce guide n’est pas une théorie de slides : c’est le retour d’une migration réelle, avec son architecture, son code, ses pièges et ses chiffres après deux mois de production. Voici tout ce qu’un développeur WordPress ou front doit comprendre avant de se lancer.

Le principe du headless : WordPress comme back-office

Le headless consiste à découper WordPress en deux : d’un côté le CMS, qui conserve son rôle de back-office (rédaction, médiathèque, rôles, workflow éditorial), de l’autre un front totalement découplé qui ne dépend plus du thème PHP. WordPress ne génère plus le HTML envoyé au visiteur ; il expose ses contenus via une API, et c’est Next.js qui se charge du rendu. On « décapite » donc la couche de présentation, d’où le terme headless.

Concrètement, l’admin reste familier pour les rédacteurs : ils publient comme avant. Mais la requête d’un internaute ne touche plus le serveur PHP. Dans la migration décrite ici, l’architecture finale enchaîne le visiteur, le CDN Cloudflare, puis Vercel qui sert un Next.js 15 en ISR 60 secondes. WordPress, hébergé chez o2switch, ne sert plus que l’administration, et l’API WPGraphQL + ACF est rendue privée derrière un VPN. Le front public et le back-office deviennent deux mondes séparés, reliés par une seule chose : des données.

Ce découplage change la nature du projet. On ne « thème » plus WordPress, on construit une application JavaScript qui consomme une source de contenu. Cela impose de repenser des évidences du monde monolithique : les menus, les URL, l’aperçu, le SEO. Chacune de ces briques, automatique dans un thème classique, doit être reconstruite côté front. C’est le prix de la liberté, et la première chose à mesurer avant de migrer.

API REST WordPress contre WPGraphQL

Pour exposer les contenus, deux voies existent. La première est l’API REST, intégrée nativement à WordPress depuis des années et disponible sans plugin sous /wp-json/wp/v2/. Elle renvoie du JSON pour les articles, pages, médias, taxonomies. Son atout est la simplicité : aucune installation, une documentation officielle solide, des endpoints prévisibles. Son défaut est la sur-récupération : pour afficher trois champs d’un article, on télécharge souvent l’objet complet, et croiser article + auteur + média demande plusieurs requêtes en cascade.

La seconde voie est WPGraphQL, un plugin qui expose WordPress via une API GraphQL. On décrit précisément les champs voulus dans une seule requête, ce qui élimine le sur-fetch et réduit le nombre d’allers-retours. Couplé à Advanced Custom Fields (ACF) et à son extension WPGraphQL for ACF, il rend les champs personnalisés directement interrogeables. C’est le choix retenu dans cette migration : ACF et WPGraphQL ont été les deux seuls plugins conservés après le nettoyage. Pour un front riche en données structurées, GraphQL fait gagner un temps considérable.

Récupérer les contenus dans Next.js (App Router)

Avec l’App Router de Next.js 15, les pages sont des React Server Components asynchrones : on peut donc appeler l’API WordPress directement dans le composant, côté serveur, sans exposer de clé ni alourdir le bundle client. Une page d’article récupère le contenu par son slug, puis le rend. Voici un exemple réel, avec une requête WPGraphQL exécutée via fetch dans un Server Component :

// app/[slug]/page.tsx — React Server Component
async function fetchGraphQL(query, variables) {
  const res = await fetch(process.env.WP_GRAPHQL_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ query, variables }),
    next: { revalidate: 60 }, // ISR : 60s
  });
  const { data } = await res.json();
  return data;
}

export async function generateStaticParams() {
  const data = await fetchGraphQL(
    `{ posts(first: 1000) { nodes { slug } } }`, {});
  return data.posts.nodes.map((p) => ({ slug: p.slug }));
}

export default async function ArticlePage({ params }) {
  const { post } = await fetchGraphQL(
    `query Post($slug: ID!) {
       post(id: $slug, idType: SLUG) {
         title content date author { node { name } }
       }
     }`, { slug: params.slug });
  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </article>
  );
}

La fonction generateStaticParams liste à la build tous les slugs à pré-générer, de sorte que chaque article devient une page statique. Le content renvoyé par WordPress reste du HTML : on l’injecte via dangerouslySetInnerHTML, en gardant à l’esprit qu’il faut maîtriser sa source. L’équivalent en REST consisterait à appeler /wp-json/wp/v2/posts?slug=... et à lire le premier élément du tableau retourné.

Rendu statique, ISR et revalidation

Le gain de performance vient surtout du rendu statique. Plutôt que d’exécuter PHP à chaque visite, Next.js génère des pages HTML à la build et les sert depuis le CDN. Le problème évident d’un site éditorial : le contenu change. C’est là qu’intervient l’ISR (Incremental Static Regeneration). Avec l’option next: { revalidate: 60 }, une page reste servie en statique mais se régénère en arrière-plan au maximum toutes les 60 secondes après une requête. Le visiteur a toujours une page instantanée, et le contenu reste frais sans rebuild complet.

Dans cette migration, l’ISR à 60 secondes a été le cœur du dispositif : il combine la vitesse du statique et la souplesse du dynamique. Pour des mises à jour immédiates après publication, on peut compléter par une revalidation à la demande déclenchée par un webhook WordPress qui appelle revalidatePath ou revalidateTag dès qu’un article est sauvegardé. On choisit ainsi, page par page, entre fraîcheur garantie et coût de calcul. Le résultat mesuré parle de lui-même : le score Lighthouse est passé de 34 à 97.

Menus, médias et SEO (Yoast en mode headless)

Trois automatismes du thème classique disparaissent et doivent être reconstruits. Les menus d’abord : en headless, ils ne s’affichent plus tout seuls. On les expose via WPGraphQL (extension menus) ou l’API REST, puis on les rend en composant React. Les médias ensuite : les URL pointent toujours vers WordPress, ce qui pose un problème de poids et de domaine. Ici, 83% du poids des pages venait d’images PNG de 2 à 3 Mo. La solution a combiné une conversion WebP automatique côté WordPress avec le plugin WebP Express et le composant next/image côté front, pour une réduction de 83% du poids des images.

Le SEO est le point le plus sensible. Yoast génère normalement les balises title, meta description, Open Graph et le JSON-LD directement dans le thème PHP — qui n’existe plus. En headless, il faut récupérer ces métadonnées via l’extension WPGraphQL for Yoast SEO (ou l’API REST Yoast) et les réinjecter dans le generateMetadata de Next.js. Sans cette étape, on perd les canoniques, les balises sociales et le balisage structuré : un risque direct pour le référencement. Bien câblé, le résultat est au rendez-vous, avec +22% de trafic organique deux mois après la bascule.

Authentification et aperçu : le vrai casse-tête

Tant qu’on lit du contenu publié, tout va bien : l’API peut rester publique en lecture. Mais deux besoins exigent de l’authentification. Le premier est l’accès aux brouillons et aux champs protégés, qui passe par un jeton — typiquement JWT via un plugin dédié — ajouté en en-tête de la requête. Le second, plus subtil, est la prévisualisation : un rédacteur veut voir son brouillon rendu par le front avant publication. C’est le problème le plus complexe du headless, et il a coûté deux jours de debug dans cette migration.

La solution mise en place repose sur le Draft Mode de Next.js. Un endpoint /api/preview reçoit l’identifiant du post et un token JWT, vérifie la légitimité de la demande, active le Draft Mode, puis redirige vers la page concernée. En Draft Mode, les Server Components passent du rendu statique au rendu dynamique et interrogent l’API avec le jeton, ce qui permet d’afficher le brouillon non publié. Sécuriser ce flux est crucial : un endpoint d’aperçu mal protégé expose des contenus confidentiels et offre un vecteur d’attaque. C’est ici que le découplage se paie en complexité.

Déploiement sur Vercel et nettoyage de WordPress

Côté infrastructure, le front Next.js se déploie naturellement sur Vercel, qui gère le build, l’ISR, les fonctions serveur et la distribution sur son edge. On y connecte le dépôt Git, on renseigne les variables d’environnement (URL de l’API WordPress, secret de preview, token), et chaque push déclenche un déploiement. Cloudflare est placé devant en CDN supplémentaire. WordPress, lui, reste sur son hébergement mutualisé mais n’est plus exposé au trafic public : l’API est placée derrière un VPN, ce qui réduit drastiquement la surface d’attaque.

La migration a commencé par une journée de nettoyage de WordPress : suppression de huit des douze plugins, en ne gardant qu’ACF et WPGraphQL, et réduction du thème à sa plus simple expression puisqu’il ne sert plus qu’à l’admin. Le front Next.js 15 a ensuite demandé cinq jours de développement. Un détail a piégé la migration : WordPress avait laissé créer sept articles partageant le même slug /news. Comme le front s’appuie sur des slugs uniques pour router, un nettoyage manuel a été obligatoire avant la bascule. Le headless ne pardonne pas les données incohérentes.

Avantages, inconvénients : pour qui ?

Les bénéfices sont nets et mesurables. La performance d’abord : le temps de chargement est passé de 4,7 à 0,6 seconde et le Lighthouse de 34 à 97, grâce au statique et à l’ISR. La sécurité ensuite : en sortant wp-admin et l’API du trafic public, les 3000+ attaques quotidiennes sont tombées à zéro. L’expérience développeur enfin : on travaille en React, TypeScript et Git, avec un écosystème front moderne, loin des contraintes du thème PHP. Pour une équipe technique, le confort est réel.

Les inconvénients sont tout aussi concrets. La complexité explose : menus, SEO, aperçu, authentification et médias doivent être reconstruits un à un. Tout l’écosystème de plugins front de WordPress (formulaires, sliders, pop-ups, optimisation) devient inopérant côté public, puisque ces plugins injectent du HTML et du JavaScript dans un thème absent. Le coût augmente aussi : hébergement Vercel, maintenance de deux stacks, compétences front exigées. La règle pratique tient en une phrase : pour un site vitrine de cinq pages, restez sur un bon thème WordPress optimisé ; pour un site de contenu de 100 articles et plus, le headless devient pleinement justifié.

Sources

W
WP Admin Lab

Architecte web full-stack. WordPress, performance, data et sécurité. Notes de terrain, tests reproductibles et retours d'expérience.