Avant de recommander Next.js à un client pour son projet headless, je préfère souvent lui montrer ce qu’une application React « nue » peut faire en une heure, sans framework de méta-rendu. Ça permet de comprendre ce que Next.js ajoute réellement (rendu serveur, routage basé fichiers, optimisation d’images) avant de payer sa complexité. Cet article construit une SPA React minimale, avec Vite, qui consomme l’API REST de WordPress.
L’objectif n’est pas la performance ni le SEO — une SPA pure côté client reste faible sur ces deux points — mais la compréhension du flux de données brut entre WordPress et un front JavaScript, sans intermédiaire.
Initialiser le projet avec Vite
Vite génère un projet React fonctionnel en quelques secondes, sans configuration Webpack à écrire :
npm create vite@latest mon-front-wp -- --template react
cd mon-front-wp
npm install
npm install react-router-dom
npm run dev
Le serveur de développement démarre en local, généralement sur http://localhost:5173. À ce stade, l’application n’affiche que la page par défaut de Vite : il reste à brancher le routage et les appels à l’API.
Mettre en place le routage avec react-router-dom
Une SPA a besoin d’un routeur côté client pour simuler la navigation entre une liste d’articles et une page de détail, sans rechargement de page :
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import ListeArticles from './pages/ListeArticles';
import DetailArticle from './pages/DetailArticle';
export default function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<ListeArticles />} />
<Route path="/article/:slug" element={<DetailArticle />} />
</Routes>
</BrowserRouter>
);
}
Récupérer la liste des articles
La liste s’appuie sur un fetch classique vers /wp-json/wp/v2/posts, avec gestion explicite des trois états attendus par l’utilisateur : chargement, erreur, données disponibles.

import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
const API = 'https://exemple.fr/wp-json/wp/v2';
export default function ListeArticles() {
const [articles, setArticles] = useState(null);
const [erreur, setErreur] = useState(null);
useEffect(() => {
fetch(`${API}/posts?_fields=id,slug,title.rendered,excerpt.rendered`)
.then((res) => {
if (!res.ok) throw new Error(`Erreur ${res.status}`);
return res.json();
})
.then(setArticles)
.catch((err) => setErreur(err.message));
}, []);
if (erreur) return <p>Impossible de charger les articles : {erreur}</p>;
if (!articles) return <p>Chargement…</p>;
return (
<ul>
{articles.map((article) => (
<li key={article.id}>
<Link to={`/article/${article.slug}`}>
<span dangerouslySetInnerHTML={{ __html: article.title.rendered }} />
</Link>
</li>
))}
</ul>
);
}
L’usage de dangerouslySetInnerHTML n’a rien d’optionnel ici : le champ title.rendered contient déjà du HTML échappé par WordPress (entités comme ’ pour une apostrophe typographique), qu’un simple {article.title.rendered} afficherait tel quel plutôt que de le décoder.
Récupérer un article par son slug
L’API REST WordPress ne permet pas de demander un article directement par son slug dans l’URL de la route : il faut passer par un paramètre de requête slug sur la route de liste, qui renvoie un tableau à un seul élément.
useEffect(() => {
fetch(`${API}/posts?slug=${slug}&_fields=id,title.rendered,content.rendered,date`)
.then((res) => res.json())
.then((data) => setArticle(data[0] ?? null));
}, [slug]);
États de chargement : ne pas les négliger
- Un état de chargement affiché immédiatement, même bref, évite un flash de contenu vide perçu comme un bug.
- Un état d’erreur explicite (plutôt qu’un écran blanc silencieux) est indispensable dès que l’API est temporairement indisponible, ce qui arrive en production plus souvent qu’on ne l’imagine.
- Un état « aucun résultat » distinct de l’état d’erreur, pour un slug qui ne correspond à aucun article.
Déploiement statique
Une SPA React construite avec Vite génère un dossier dist composé uniquement de fichiers statiques (HTML, JS, CSS), déployable sur n’importe quel hébergeur statique sans serveur Node :
npm run build
# déployer le dossier dist/ sur Netlify, Vercel ou un simple serveur Apache/Nginx
Un point d’attention pour le routage côté client : le serveur doit rediriger toutes les routes inconnues vers index.html, sinon un rechargement direct sur /article/mon-slug renverra une erreur 404 côté serveur, le routeur React n’ayant pas eu l’occasion de s’exécuter.
Cette approche minimale convient à un prototype ou à une interface interne, jamais à un site public dont le référencement compte : sans rendu serveur, les moteurs de recherche voient une page vide au premier chargement.
En résumé
Une SPA React sans framework de méta-rendu reste la façon la plus rapide de comprendre les bases du headless : appels REST, gestion des états, routage côté client. Elle atteint vite ses limites dès que le SEO ou la performance au premier chargement deviennent des exigences du projet, ce qui oriente naturellement vers des solutions avec rendu serveur ou statique.