
WordPress headless, c’est utiliser WordPress uniquement pour gérer le contenu (administration, éditeur, médias, utilisateurs) et confier l’affichage du site à une application séparée, ici Next.js, qui lit ce contenu par l’API REST ou par WPGraphQL. On y gagne un front rapide et entièrement libre. On y perd ce que le thème et les extensions faisaient tout seuls : balises SEO, aperçu des brouillons, formulaires, panier. Et il faut héberger et maintenir deux applications au lieu d’une.
Après quatorze ans de WordPress classique, je me suis mis au headless avec Next.js sur les projets qui s’y prêtent, mais pas partout : pour un site vitrine ou un blog, un WordPress classique bien construit reste souvent le meilleur choix. Ce guide, à jour pour WordPress 7.1 et Next.js 16.4, explique le fonctionnement, les apports réels, les limites et mes critères de choix.
WordPress headless : comment ça fonctionne
Le WordPress classique : le thème fabrique chaque page
Dans un WordPress classique, tout se passe au même endroit : à chaque visite (ou depuis le cache de page), PHP interroge la base de données, le thème assemble le HTML et les extensions s’y greffent (balises SEO, formulaire de contact, bandeau de cookies, panier WooCommerce). Ce couplage rend WordPress simple à faire évoluer, mais le rend dépendant de la qualité du thème.
Le headless : WordPress gère le contenu, Next.js l’affiche
En headless (on parle aussi de CMS découplé), le travail se fait en trois temps :
- les rédacteurs écrivent dans l’administration WordPress habituelle, avec l’éditeur de blocs, ACF et les médias ;
- Next.js lit ces contenus par l’API, au format JSON ;
- Next.js fabrique les pages HTML, les met en cache et les sert aux visiteurs, souvent derrière un CDN.
La « tête » (le thème) est retirée : WordPress devient une source de contenu, qui peut aussi alimenter une application mobile ou un second site.
Ce qui reste en ligne côté WordPress
WordPress ne disparaît pas d’Internet : l’administration (/wp-admin/), la page de connexion, l’API (/wp-json/ ou /graphql) et les médias restent accessibles, puisque Next.js et les rédacteurs en ont besoin. On réduit la surface d’attaque du site public, pas celle du CMS. Le plus sûr est de placer WordPress sur un sous-domaine dédié, de protéger l’administration comme sur tout site et de garder le même rythme de mises à jour.
Il faut aussi empêcher l’indexation de l’ancien front WordPress : redirection vers le domaine Next.js (en épargnant l’administration, l’API, les médias et l’aperçu) ou, à défaut, noindex. Pas de code tout fait ici : une redirection mal réglée casse l’aperçu ou l’API.
| Critère | WordPress classique | WordPress headless + Next.js |
|---|---|---|
| Qui fabrique le HTML | Le thème PHP, à chaque visite ou depuis le cache de page | Next.js, à l’avance ou à la requête, puis mis en cache |
| Hébergement | Un serveur PHP et une base de données | WordPress (PHP) plus une application Node.js ou une plateforme dédiée |
| Extensions qui affichent quelque chose (formulaires, cookies, panier) | Fonctionnent telles quelles | À refaire dans Next.js à partir de leurs API, quand elles en ont |
| Balises SEO, sitemap, redirections | Produits par Rank Math ou Yoast | Lus dans l’API et rendus par Next.js |
| Aperçu des brouillons | Bouton « Prévisualiser » natif | À brancher (Draft Mode de Next.js) |
| Page builders (Avada, Elementor) | Oui | Non : mises en page à refaire en composants |
| Compétences | PHP, thème, éditeur de blocs | PHP et API WordPress, plus React, TypeScript et Next.js |
| Maintenance | Une application à mettre à jour | Deux applications, deux séries de mises à jour |
Récupérer le contenu : API REST ou WPGraphQL
L’API REST native de WordPress
Les routes de contenu de l’API REST sont dans le cœur depuis WordPress 4.7, fin 2016. Sans rien installer, /wp-json/wp/v2/posts, pages, media, categories ou users renvoient du JSON. Trois paramètres comptent particulièrement en headless :
- la pagination : l’API ne renvoie pas « tous vos articles », mais 10 par défaut et 100 au maximum avec
per_page; on parcourt les pages avecpage, et les en-têtesX-WP-TotaletX-WP-TotalPagesdonnent les totaux (documentation de la pagination) ; _fields: ne demander que les champs utiles allège nettement la réponse ;_embed: inclure dans la même réponse l’image mise en avant, l’auteur ou les catégories.
GET https://cms.example.com/wp-json/wp/v2/posts?per_page=10&page=2&_fields=id,slug,date,title,excerpt
# En-têtes de la réponse utiles à la pagination :
X-WP-Total: 148
X-WP-TotalPages: 15
Les types de contenu personnalisés n’apparaissent dans l’API que s’ils sont déclarés avec show_in_rest. Les champs ACF ne sont pas exposés par défaut : il faut activer l’option « Show in REST API » du groupe de champs, et ils arrivent alors sous la clé acf (documentation ACF). Pour des données métier qui ne rentrent dans aucune route existante, on écrit ses propres routes avec register_rest_route(), avec un contrôle des droits sur chacune : c’est typiquement une extension WordPress sur mesure qui expose vos données dans l’API. Si vous voulez le faire vous-même, commencez par apprendre à développer une extension WordPress.
WPGraphQL et WPGraphQL for ACF
WPGraphQL ajoute une API GraphQL à WordPress. Une seule requête ramène exactement les champs voulus, relations comprises : un article, son auteur, ses catégories, ses champs ACF (avec l’extension WPGraphQL for ACF) et les articles liés. Les deux extensions sont gratuites sur WordPress.org et maintenues activement. Les contreparties : une extension de plus à mettre à jour sur un composant critique, et un cache HTTP moins simple, car les requêtes GraphQL partent souvent en POST.
REST ou WPGraphQL : comment je choisis
Mon critère est simple. Pour un site éditorial aux contenus peu liés, je reste sur l’API REST : rien à installer, des URL faciles à mettre en cache, une documentation officielle. Quand le modèle de contenu devient riche (nombreux champs ACF, relations entre types de contenu, menus, contenus imbriqués), WPGraphQL évite les allers-retours et rend le code du front plus lisible, surtout si l’équipe connaît déjà GraphQL.
Brouillons, contenus privés et authentification
Les contenus publiés se lisent sans authentification. Les brouillons, les contenus privés et toute écriture demandent une requête authentifiée. Les mots de passe d’application, intégrés au cœur depuis WordPress 5.6, suffisent pour des appels de serveur à serveur ; les jetons JWT passent par une extension. Règle absolue : aucun identifiant dans le code exécuté par le navigateur. Les appels authentifiés partent du serveur Next.js (Server Components, Route Handlers), avec des variables d’environnement non préfixées par NEXT_PUBLIC_.
Rendu et cache côté Next.js : statique, dynamique et régénération
App Router et Server Components
Depuis Next.js 13.4, l’App Router (dossier app/) est le modèle recommandé ; le Pages Router (pages/) reste supporté, mais il n’y a plus de raison d’y démarrer un nouveau projet. Ses Server Components appellent WordPress côté serveur, sans exposer l’API au navigateur ni lui envoyer de JavaScript inutile ; seuls les éléments interactifs (menu mobile, recherche, filtres) sont des composants client.
Pages prérendues, ISR et Cache Components
SSG (pages générées à l’avance), SSR (rendues à la requête) et ISR (régénération incrémentale) décrivent toujours les choix possibles. Depuis Next.js 16, on peut les exprimer avec les Cache Components : on marque une fonction ou un composant avec la directive 'use cache', on fixe sa durée de vie avec cacheLife() et on lui pose une étiquette avec cacheTag(). Depuis Next.js 16.4 (octobre 2026), les nouveaux projets créés avec create-next-app ont l’option cacheComponents activée par défaut, et l’équipe de Next.js annonce que ce modèle deviendra le comportement par défaut avec Next.js 17 (annonce de Next.js 16.4). Sur un projet plus ancien, on l’active dans next.config.ts, ou l’on reste sur l’ancien modèle (export const revalidate, option next.revalidate de fetch), toujours documenté.
Pour un contenu de CMS, la documentation de Next.js recommande une durée longue et une invalidation à la demande. C’est ce que fait cette fonction :
// lib/wordpress.ts
import { cacheLife, cacheTag } from 'next/cache'
const API = process.env.WP_API_URL // ex. https://cms.example.com/wp-json
export type WPPost = {
id: number
slug: string
date: string
title: { rendered: string }
excerpt: { rendered: string }
}
export async function getPosts(): Promise<WPPost[]> {
'use cache'
cacheLife('max') // longue durée : régénéré au plus tard tous les 30 jours
cacheTag('posts') // étiquette invalidée à la publication
const res = await fetch(
`${API}/wp/v2/posts?per_page=10&_fields=id,slug,date,title,excerpt`
)
if (!res.ok) {
throw new Error(`WordPress a répondu ${res.status}`)
}
return res.json()
}
Mettre à jour le site à la publication
Reste à prévenir Next.js quand un article est publié ou modifié. Une route du front appelle revalidateTag('posts', 'max') : les pages concernées sont marquées comme périmées, le visiteur suivant reçoit encore l’ancienne version pendant que la nouvelle se prépare (documentation de la revalidation). La forme à un seul argument de revalidateTag est dépréciée, et updateTag est réservé aux Server Actions : il ne sert pas pour un webhook.
// app/api/revalidate/route.ts
import { revalidateTag } from 'next/cache'
import type { NextRequest } from 'next/server'
export async function POST(request: NextRequest) {
const secret = process.env.REVALIDATE_SECRET
if (!secret || request.headers.get('x-revalidate-secret') !== secret) {
return Response.json({ message: 'Non autorisé' }, { status: 401 })
}
// Étiquette fixée ici : un appel extérieur ne peut pas invalider n'importe quel cache.
revalidateTag('posts', 'max')
return Response.json({ revalidated: true })
}
Côté WordPress, un petit mu-plugin envoie cette requête depuis le hook wp_after_insert_post (WordPress 5.6 et plus), déclenché une fois l’article, ses termes et ses métadonnées enregistrés. L’URL et le secret restent dans wp-config.php, et l’appel est non bloquant pour ne pas ralentir l’enregistrement.
<?php
/**
* Plugin Name: Revalidation du front Next.js
* Description: Prévient le front Next.js quand un article publié change.
*
* À placer dans wp-content/mu-plugins/. Constantes à définir dans wp-config.php :
* define( 'DEVWP_NEXT_REVALIDATE_URL', 'https://www.example.com/api/revalidate' );
* define( 'DEVWP_NEXT_REVALIDATE_SECRET', 'une-longue-chaine-aleatoire' );
*/
defined( 'ABSPATH' ) || exit;
add_action( 'wp_after_insert_post', 'devwp_headless_revalidate', 10, 4 );
/**
* Demande au front de régénérer ses pages quand un article publié change.
*
* @param int $post_id ID du contenu.
* @param WP_Post $post Contenu enregistré.
* @param bool $update Mise à jour d'un contenu existant.
* @param WP_Post|null $post_before Contenu avant l'enregistrement.
*/
function devwp_headless_revalidate( $post_id, $post, $update, $post_before ) {
if ( ! defined( 'DEVWP_NEXT_REVALIDATE_URL' ) || ! defined( 'DEVWP_NEXT_REVALIDATE_SECRET' ) ) {
return;
}
if ( 'post' !== $post->post_type || wp_is_post_revision( $post ) || wp_is_post_autosave( $post ) ) {
return;
}
// Seuls les contenus publiés, ou qui viennent de quitter l'état publié, concernent le front.
$was_published = $post_before instanceof WP_Post && 'publish' === $post_before->post_status;
if ( 'publish' !== $post->post_status && ! $was_published ) {
return;
}
wp_remote_post(
DEVWP_NEXT_REVALIDATE_URL,
array(
'timeout' => 5,
'blocking' => false,
'headers' => array(
'x-revalidate-secret' => DEVWP_NEXT_REVALIDATE_SECRET,
),
)
);
}
Si chaque article a sa propre étiquette, la route peut recevoir le slug, le valider et n’invalider que lui. Je préfère fixer l’étiquette côté serveur dès que possible : un appel forgé ne peut alors rien invalider d’autre.
Ce que le headless apporte vraiment
Bien utilisé, le headless apporte quatre bénéfices concrets.
- Des pages rapides par construction. Le HTML est prérendu et servi depuis un cache ou un CDN : aucune requête PHP ni base de données à chaque visite pour les pages en cache. Il faut tout de même surveiller le JavaScript envoyé par les composants client, et configurer
next/imagepour les images servies par WordPress (domaine distant à autoriser, hébergement capable d’optimiser les images ou loader dédié). - Un front entièrement libre. Composants React sur mesure, design system, Tailwind CSS : je construis l’interface voulue sans contourner un thème, y compris des fonctions applicatives (recherche instantanée, filtres, espaces connectés).
- Un contenu pour plusieurs sorties. Le site, une application mobile et d’autres fronts lisent la même API.
- Des équipes découplées. Les rédacteurs restent dans WordPress, les développeurs front dans leur outillage (TypeScript, tests, déploiement continu).

Cette capture, publiée avec la première version de cet article, provient d’une ancienne version de Lighthouse (la catégorie PWA et l’indicateur Time to Interactive ont depuis disparu). C’est une mesure de laboratoire sur une page, pas une garantie pour tout un site ; et le score « SEO » de Lighthouse vérifie quelques bases techniques, ce n’est pas un classement.
Soyons justes : un WordPress classique bien construit (thème léger, cache de page, bon hébergement, scripts chargés seulement là où ils servent) peut lui aussi être très rapide. Avant d’envisager le headless pour des raisons de performance, je commence par optimiser le chargement des scripts et des styles du site existant.
Les limites que l’on découvre en production
Le SEO est à reconstruire côté Next.js
Le rendu serveur rend le HTML indexable, c’est vrai. Mais en headless, Rank Math ou Yoast ne produisent plus les balises du site public : title, meta description, canonical, Open Graph, données structurées, sitemap, redirections et robots.txt doivent être exposés par WordPress puis rendus par Next.js.
- Rank Math : l’option « Headless CMS Support » (réglages généraux, onglet « Others » ou « Autres », visible en mode avancé) active la route
/wp-json/rankmath/v1/getHead?url=…, qui renvoie le HTML des balises de la page (documentation Rank Math). Il faut ensuite en tirer les valeurs utiles côté Next.js. - Yoast SEO : il ajoute les champs
yoast_headetyoast_head_jsonaux réponses de l’API, et propose la route/wp-json/yoast/v1/get_head?url=…(documentation Yoast). La version JSON se transpose facilement. - Avec WPGraphQL, des extensions communautaires exposent les données de Yoast ou de Rank Math dans le schéma GraphQL : vérifiez leur maintenance avant de vous en servir.
Côté Next.js, on rend ces données avec generateMetadata, on génère app/sitemap.ts et app/robots.ts, on reporte les redirections (celles de Rank Math ne s’appliquent plus au front), on fait pointer les canonical vers le domaine du front et l’on injecte les données structurées en JSON-LD. Sans oublier le piège évoqué plus haut : un domaine WordPress resté indexable duplique tout le contenu.
L’aperçu, les blocs et les page builders
Le bouton « Prévisualiser » de WordPress affiche le thème, pas le vrai site. Il faut brancher le Draft Mode de Next.js : une route protégée par un secret active un cookie, et tant qu’il est présent, Next.js ignore ses caches (y compris les fonctions 'use cache') et lit le brouillon, par une requête authentifiée vers WordPress. Faust.js, la boîte à outils headless de WP Engine (qui s’appuie sur WPGraphQL), propose aussi un aperçu prêt à l’emploi.
Le contenu de l’éditeur de blocs arrive en HTML (content.rendered) : il faut charger les styles des blocs et n’injecter ce HTML tel quel (dangerouslySetInnerHTML) que si ses auteurs sont de confiance, sinon l’assainir.
Les page builders sont le cas le plus délicat. Une mise en page Avada Builder ou Elementor est fabriquée par le rendu PHP de WordPress, avec ses propres feuilles de style et scripts : elle ne s’affiche pas telle quelle dans Next.js. Un site construit ainsi ne « passe » pas en headless : il est refait en composants, et le client perd l’édition visuelle de ses pages, ce qui compte souvent plus que quelques points de Lighthouse.
Les extensions qui agissent sur le front
Toutes les extensions continuent de tourner dans l’administration, mais tout ce qu’elles affichent aux visiteurs est à refaire : formulaires (à reconstruire et à envoyer vers une API, celle de l’extension si elle en propose une), bandeau de cookies, recherche, commentaires, multilingue. Les extensions de cache de page comme WP Rocket ne servent plus le front. Pour WooCommerce, le panier, le compte client et le tunnel de commande passent par la Store API ou par WPGraphQL for WooCommerce : c’est un vrai projet de développement, pas une option à cocher.
Deux applications à héberger, surveiller et mettre à jour
D’un côté WordPress, sur un serveur PHP à jour (WordPress recommande PHP 8.3 ou plus, avec MySQL 8.0 ou MariaDB 10.11 et au-delà ; sur un nouveau serveur, autant partir sur PHP 8.4 ou 8.5, en support actif jusqu’à fin 2026 pour la 8.4 et fin 2027 pour la 8.5). De l’autre, Next.js, sur un serveur Node.js ou une plateforme dédiée. Cela fait deux déploiements, deux séries de mises à jour (cœur et extensions d’un côté, Next.js et dépendances npm de l’autre), des secrets, la configuration CORS si le navigateur appelle l’API et deux sauvegardes : le temps de maintenance augmente d’autant.
Le coût de développement et les compétences
Un projet headless demande React, TypeScript, Next.js et l’API de WordPress. Le développement initial est plus long qu’avec un thème, car tout ce que WordPress faisait seul doit être recâblé ; ensuite, la moindre évolution de mise en page passe par un développeur, alors qu’un client modifie seul une page Avada ou Elementor.
Quand le headless se justifie, et quand rester sur un WordPress classique
Les cas où je le recommande
- un front applicatif riche : recherche instantanée, filtres complexes, espace client, configurateur ;
- un contenu partagé entre un site et une application mobile, ou entre plusieurs fronts ;
- une équipe ou un produit déjà en React et Next.js, qui veut donner WordPress aux rédacteurs ;
- un gros volume éditorial avec beaucoup de trafic, et peu de fonctions front qui dépendent d’extensions.
Les cas où un WordPress classique bien construit suffit
Site vitrine, blog, boutique WooCommerce standard, client qui veut modifier ses pages en visuel, budget serré : dans tous ces cas, je reste sur un WordPress classique. Un thème léger, un cache de page et un bon hébergement règlent la question de la performance, sans doubler la maintenance. Pour vous orienter, voyez comment choisir un thème WordPress adapté à votre projet, ou ce que comprend une création de site WordPress sur mesure.
Next.js, Astro ou un CMS headless natif ?
Pour un site surtout éditorial et peu interactif, Astro produit des pages statiques très légères et se branche lui aussi sur l’API de WordPress. Si vous partez de zéro, sans rédacteurs habitués à WordPress ni contenu existant, comparez aussi un CMS headless natif comme Strapi, Sanity ou Contentful. L’atout de WordPress reste un éditeur que les équipes connaissent et un contenu déjà en place.
Ma méthode pour démarrer un projet WordPress headless
Voici l’ordre que je recommande, pour ne découvrir aucune limite après la mise en ligne :
- Inventorier ce que le thème et les extensions font côté visiteur : formulaires, SEO, cookies, recherche, panier.
- Modéliser les contenus : types de contenu, taxonomies, champs ACF, avec
show_in_restou leur équivalent GraphQL. - Choisir l’API : REST pour un modèle simple, WPGraphQL pour des contenus très liés.
- Prévoir le SEO dès le départ : balises de Rank Math ou de Yoast lues dans l’API, sitemap, robots.txt, redirections, canonical.
- Brancher l’aperçu avec le Draft Mode et une requête authentifiée.
- Brancher la revalidation à la publication, avec un secret.
- Verrouiller le WordPress source : front redirigé ou non indexable, administration protégée, mises à jour suivies.
- Documenter pour les rédacteurs ce qui change : aperçu, délai de mise à jour, champs à remplir.
Questions fréquentes sur WordPress headless
En résumé
Le headless n’est pas une mise à niveau de WordPress, c’est un choix d’architecture. Il donne un front rapide et libre, au prix d’un SEO, d’un aperçu et de fonctions à reconstruire, et de deux applications à maintenir. Je l’utilise quand le front l’exige, pas pour accélérer un site vitrine qu’un WordPress classique bien construit servirait aussi bien.
Vous hésitez entre un WordPress classique et une architecture headless pour votre projet ? Décrivez-moi votre besoin : je vous dirai franchement laquelle me semble adaptée.
Mis à jour en octobre 2026 par un développeur WordPress freelance senior.