# Une SPA React qui consomme l’API REST de WordPress, sans framework

> Pas besoin de Next.js pour démarrer : une petite application React avec Vite, quelques routes et un fetch suffisent pour explorer le headless.

- Auteur : Clément Hadrot
- Publié le : 2020-11-02
- Mis à jour le : 2020-11-02
- Catégorie : Headless &amp; API
- URL : https://wpmoderne.dev.wordpress-developpement.fr/headless/spa-react-api-rest-wordpress-sans-framework/

## L’essentiel

- Vite pour un démarrage minimal, sans configuration lourde
- Gestion des routes et des états de chargement à la main
- Déploiement statique simple, sans serveur Node

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.

> L'essentiel à retenir : Vite pour un démarrage minimal, sans configuration lourde ; Gestion des routes et des états de chargement à la main ; Déploiement statique simple, sans serveur Node

```
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 `&#8217;` 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.
