Agence WordPress Be API | Actualités | WordPress | WordPress headless avec React : pagination, menus et pages

WordPress headless avec React : pagination, menus et pages

Publié le

par

Dans notre premier article, nous avons vu comment créer une application React basique qui affiche les articles d’un site WordPress via son API REST. Nous avions mis en place les fondamentaux : récupération des données des articles, routage, ajout de Tailwind CSS et styling de base.

Aujourd’hui, nous allons aller plus loin en ajoutant des fonctionnalités essentielles pour un site web complet :

  • Pagination pour naviguer entre les pages d’articles
  • Menus de navigation (header et footer) récupérés via l’API
  • Pages WordPress (et pas seulement les articles)

Ces ajouts vont transformer notre application basique en un véritable site web fonctionnel.

1. Mise en place de la pagination

La pagination est essentielle quand on a beaucoup d’articles. Par défaut, l’API WordPress retourne 10 articles par page, mais nous pouvons configurer ce nombre et naviguer entre les pages.

1.1 Comprendre la pagination de l’API WordPress

L’API WordPress propose plusieurs moyens de gérer la pagination :

  • per_page : nombre d’éléments par page (par défaut 10, maximum 100)
  • page : numéro de la page à récupérer
  • Headers de réponse : X-WP-Total (nombre total d’éléments) et X-WP-TotalPages (nombre total de pages)

Par exemple, pour récupérer la page 2 avec 5 articles par page :

GET /wp-json/wp/v2/posts?page=2&per_page=5

Note importante : L’exemple ci-dessus utilise l’API standard WordPress. Pour ce tutoriel, nous utiliserons l’API publique de WordPress.com qui a une structure légèrement différente. Il s’agit ici d’utiliser wordpress.com afin d’avoir du contenu qui ne soit pas bridé (comme sur fr.wordpress.org) pour la suite du tutoriel.

Différence entre API WordPress standard et WordPress.com

API WordPress standard (votre-site.com) :

https://votre-site.com/wp-json/wp/v2/posts
https://votre-site.com/wp-json/wp/v2/pages

API WordPress.com publique :

https://public-api.wordpress.com/wp/v2/sites/nom-du-site/posts
https://public-api.wordpress.com/wp/v2/sites/nom-du-site/pages

Dans notre code, nous configurons donc l’URL de base comme ceci :

VITE_WP_API_URL=https://public-api.wordpress.com/wp/v2/sites/en.blog.wordpress.com

Puis nos requêtes ajoutent simplement /posts, /pages, etc. pour construire l’URL complète. Cette approche permet de switcher facilement entre différentes APIs en changeant juste la variable d’environnement.

Mais pour utiliser l’API de votre propre site, conservez la structure classique:

VITE_WP_API_URL=https://votre-site.com/wp-json

1.2 Modifier les routes pour supporter la pagination

Nous avions déclaré des routes pour nos articles, il nous faut maintenant supporter la pagination. Nous allons donc modifier nos routes pour accepter un paramètre de page optionnel et ajouter le support des pages WordPress :

// app/routes.ts
import type { RouteConfig } from "@react-router/dev/routes";

export default [
	{
		path: "/",
		file: "routes/_index.tsx"
	},
	{
		path: "/page/:pageNumber", // ex: /page/2
		file: "routes/page.$pageNumber.tsx"
	},
	{
		path: "/post/:id",
		file: "routes/post.$id.tsx"
	},
	{
		path: "/:slug",
		file: "routes/$slug.tsx"
	}
] satisfies RouteConfig;

1.3 Créer le composant de pagination

Qui dit pagination dit composant. Nous devons donc créer un composant pour la pagination, ce qui permettra d’envoyer les requêtes à l’API avec le bon paramètre de page.

// app/components/Pagination.jsx
import { Link } from 'react-router';
export default function Pagination({ currentPage, totalPages, baseUrl = "" }) {
	if (totalPages <= 1) return null;

	const pages = [];
	const maxVisiblePages = 5;

	// Calcul des pages à afficher
	let startPage = Math.max(1, currentPage - Math.floor(maxVisiblePages / 2));
	let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1);

	if (endPage - startPage + 1 < maxVisiblePages) {
		startPage = Math.max(1, endPage - maxVisiblePages + 1);
	}

	// Bouton "Précédent"
	if (currentPage > 1) {
		const prevUrl = currentPage === 2 ? "/" : `/page/${currentPage - 1}`;
		pages.push(
			<Link
				key="prev"
				to={prevUrl}
				className="px-3 py-2 bg-gray-200 hover:bg-gray-300 rounded"
			>
				← Précédent
			</Link>
		);
	}

	// Pages numériques
	for (let i = startPage; i <= endPage; i++) {
		const url = i === 1 ? "/" : `/page/${i}`;
		pages.push(
			<Link
key={i}
				to={url}
				className={`px-3 py-2 rounded ${
					i === currentPage
						? 'bg-blue-500 text-white'
						: 'bg-gray-200 hover:bg-gray-300'
				}`}
			>
				{i}
			</Link>
		);
	}

	// Bouton "Suivant"
	if (currentPage < totalPages) {
		pages.push(
			<Link
				key="next"
				to={`/page/${currentPage + 1}`}
				className="px-3 py-2 bg-gray-200 hover:bg-gray-300 rounded"
			>
				Suivant →
			</Link>
		);
	}

	return (
		<nav className="flex justify-center space-x-2 mt-8">
			{pages}
		</nav>
	);
}

1.4 Mettre à jour la route d’accueil avec pagination

Puisqu’ici notre page d’accueil affiche les articles, nous devons modifier notre route d’accueil pour gérer la pagination :

// app/routes/_index.tsx
import PostsList from "../components/PostsList";
import type { LoaderFunctionArgs } from "react-router";

const API_URL = import.meta.env.VITE_WP_API_URL;
const POSTS_PER_PAGE = 5;

export async function loader({ request }: LoaderFunctionArgs) {
	try {
		const page = 1; // Page 1 pour la route d'accueil

		const response = await fetch(
			`${API_URL}/posts?page=${page}&per_page=${POSTS_PER_PAGE}&_embed`
		);

		if (!response.ok) {
			throw new Error(`API responded with status: ${response.status}`);
		}

		const posts = await response.json();

		// Récupération des headers de pagination
		const totalItems = parseInt(response.headers.get('X-WP-Total') || '0');
		const totalPages = parseInt(response.headers.get('X-WP-TotalPages') || '1');

		return {
			posts,
			pagination: {
				currentPage: page,
				totalPages,
				totalItems
			}
		};
	} catch (error) {
		console.error("Error fetching posts from server:", error);
		return {
			posts: [],
			pagination: { currentPage: 1, totalPages: 1, totalItems: 0 },
			error: error instanceof Error ? error.message : "Unknown error"
		};
	}
}

export default function Index() {
	return <PostsList />;
}

1.5 Créer la route pour les pages numérotées

Il nous faut maintenant créer la route pour les pages numérotées, c’est-à-dire les pages qui affichent les articles d’une page donnée. Par exemple, l /page/2 affichera les articles de la page 2.

// app/routes/page.$pageNumber.tsx
import PostsList from "../components/PostsList";
import type { LoaderFunctionArgs } from "react-router";

const API_URL = import.meta.env.VITE_WP_API_URL;
const POSTS_PER_PAGE = 5;

export async function loader({ params }: LoaderFunctionArgs) {
	try {
		const pageNumber = parseInt(params.pageNumber || '1');

		if (pageNumber < 1) {
			throw new Error('Page number must be positive');
		}

		// On récupère ici les articles de la page demandée
		const response = await fetch(
			`${API_URL}/posts?page=${pageNumber}&per_page=${POSTS_PER_PAGE}&_embed`
		);

		if (!response.ok) {
			throw new Error(`API responded with status: ${response.status}`);
		}

		const posts = await response.json();

		const totalItems = parseInt(response.headers.get('X-WP-Total') || '0');
		const totalPages = parseInt(response.headers.get('X-WP-TotalPages') || '1');

		return {
			posts,
			pagination: {
				currentPage: pageNumber,
				totalPages,
				totalItems
			}
		};
	} catch (error) {
		console.error("Error fetching posts from server:", error);
		return {
			posts: [],
			pagination: { currentPage: 1, totalPages: 1, totalItems: 0 },
			error: error instanceof Error ? error.message : "Unknown error"
		};
	}
}

export default function PageNumber() {
	return <PostsList />;
}

1.6 Mettre à jour le composant PostsList

Maintenant que le composant Pagination est créé, nous pouvons l’utiliser dans le composant PostsList. Il s’affichera ainsi sur la page qui liste les articles et prendra en compte le paramètre de page passé dans l’url.

// app/components/PostsList.jsx
import { Link, useLoaderData } from 'react-router';
import Pagination from './Pagination';

export default function PostsList() {
	const { posts = [], pagination, error: loaderError } = useLoaderData();

	if (loaderError) {
		return (
			<div>
				<h1>Erreur lors du chargement</h1>
				<p>Impossible de charger les articles: {loaderError}</p>
			</div>
		);
	}

	return (
		<div>
			<h1>Articles du blog</h1>
			{pagination && (
				<p className="text-gray-600 mb-6">
					Page {pagination.currentPage} sur {pagination.totalPages}
					({pagination.totalItems} articles au total)
				</p>
			)}

			<ul className="space-y-6">
				{posts.length === 0 ? (
					<p>Aucun article trouvé</p>
				) : (
					posts.map(post => (
						<li key={post.id}>
							<h2 className="text-xl font-bold mb-2">
								<Link to={`/post/${post.id}`} className="hover:text-blue-600">
									{post.title.rendered}
								</Link>
							</h2>
							<div
								className="prose text-gray-700"
								dangerouslySetInnerHTML={{ __html: post.excerpt.rendered }}
							/>
						</li>
					))
				)}
			</ul>

			{pagination && (
				<Pagination
					currentPage={pagination.currentPage}
					totalPages={pagination.totalPages}
				/>
			)}
		</div>
	);
}

2. Ajouter les menus de navigation

Vous êtes toujours là ? Ok, on continue. Nous allons à présent nous attaquer aux menus de navigation. WordPress propose une API pour récupérer les menus, mais elle nécessite l’ajout de code personnalisé dans votre propre site WordPress car par défaut les menus ne sont pas exposés via l’API REST pour des raisons de sécurité.

Important : Les endpoints de menus ne sont pas disponibles par défaut dans WordPress. Ils doivent être ajoutés via le code PHP ci-dessous dans VOTRE site WordPress. Si vous testez avec un site externe (comme fr.wordpress.org), les menus ne fonctionneront pas et l’application que l’on construit dans ce tutoriel utilisera des menus de fallback.

2.1 Activer l’API des menus dans WordPress

Pour exposer les menus via l’API REST, ajoutez ce code dans le fichier functions.php de votre thème WordPress :

// Exposer les menus via l'API REST
function wp_api_menus_init() {
    register_rest_route('wp/v2', '/menus', array(
        'methods' => 'GET',
        'callback' => 'wp_api_menus_get_all_menus',
        'permission_callback' => '__return_true'
    ));

    register_rest_route('wp/v2', '/menus/(?P<id>[a-zA-Z0-9_-]+)', array(
        'methods' => 'GET',
        'callback' => 'wp_api_menus_get_menu_data',
        'permission_callback' => '__return_true'
    ));
}

function wp_api_menus_get_all_menus($data) {
    $menus = wp_get_nav_menus();
    $result = array();

    foreach($menus as $menu) {
        $result[] = array(
            'id' => $menu->slug,
            'name' => $menu->name,
            'slug' => $menu->slug,
            'count' => $menu->count
        );
    }

    return rest_ensure_response($result);
}

function wp_api_menus_get_menu_data($data) {
    $menu_id = $data['id'];
    $menu_items = wp_get_nav_menu_items($menu_id);

    if(!$menu_items) {
        return new WP_Error('no_menu', 'Menu not found', array('status' => 404));
    }

    $menu_data = array();

    foreach($menu_items as $item) {
        $menu_data[] = array(
            'id' => $item->ID,
            'title' => $item->title,
            'url' => $item->url,
            'menu_order' => $item->menu_order,
            'parent' => $item->menu_item_parent,
            'object_id' => $item->object_id,
            'object' => $item->object,
            'type' => $item->type,
            'target' => $item->target
        );
    }

    return rest_ensure_response($menu_data);
}

add_action('rest_api_init', 'wp_api_menus_init');

Ainsi, vous exposez les menus de votre site WordPress via l’API, et notre application peut ainsi les récupérer.

2.2 Créer un composant Header avec menu

Maintenant que nous avons les menus, nous pouvons les afficher dans notre application. Nous allons donc créer un composant Header qui affichera le menu de navigation qui correspond au menu d’en-tête de votre site WordPress.

// app/components/Header.jsx
import { Link } from 'react-router';
import { useState, useEffect } from 'react';

const API_URL = import.meta.env.VITE_WP_API_URL;

export default function Header() {
	const [menuItems, setMenuItems] = useState([]);
	const [isLoading, setIsLoading] = useState(true);
	const [hasMenuSupport, setHasMenuSupport] = useState(false);

	// Menu de fallback si les menus WordPress ne sont pas disponibles (car on utilise wordpress.com)
	// ce code ne sera donc pas nécessaire pour votre propre site
	const fallbackMenu = [
		{ id: 1, title: 'Accueil', url: '/' },
		{ id: 2, title: 'Articles', url: '/' },
		{ id: 3, title: 'À propos', url: '/a-propos' }
	];

	useEffect(() => {
		async function fetchMenu() {
			try {
				// On vérifie d'abord si les endpoints de menu existent (grâce au code PHP ajouté dansfunctions.php)
				const checkResponse = await fetch(`${API_URL}/menus`);

				if (checkResponse.ok) {
					setHasMenuSupport(true);
					// On récupère le menu principal s'il existe
					const menuResponse = await fetch(`${API_URL}/menus/main-menu`);
					if (menuResponse.ok) {
						const items = await menuResponse.json();
						setMenuItems(items);
					} else {
						// Menu spécifique non trouvé, on utilise donc le fallback
						setMenuItems(fallbackMenu);
					}
				} else {
					// Pas de support des menus, on utilise le fallback
					setMenuItems(fallbackMenu);
				}
			} catch (error) {
				console.warn('Menus WordPress non disponibles, utilisation du menu de fallback:', error);
				setMenuItems(fallbackMenu);
			} finally {
				setIsLoading(false);
			}
		}

		fetchMenu();
	}, []);

	const convertWordPressUrlToRoute = (url) => {
		// Ici on manipule un peu les URLs WordPress pour les convertir en routes React
		// On convertit les URLs WordPress en routes React
		if (!url) return '/';

		try {
			// On supprime le domaine si présent
			const urlObj = new URL(url);
			let path = urlObj.pathname;

			// On supprime les slashes de fin
			path = path.replace(/\/$/, '') || '/';

			return path;
		} catch (error) {
			// Si l'URL n'est pas valide, on la retourne telle quelle
			return url;
		}
	};

	return (
		<header className="bg-white shadow-md">
			<div className="max-w-4xl mx-auto px-4 py-4">
				<div className="flex justify-between items-center">
					<Link
						to="/"
						className="text-2xl font-bold text-gray-900"
					>
						Mon Site WordPress {/* Remplacer par le titre de votre site */}
					</Link>

					<nav>
						{isLoading ? (
							{/* Tailwind nous permet de faire facilementun skeleton pour le chargement */}
							<div className="animate-pulse flex space-x-4">
								<div className="h-4 bg-gray-300 rounded w-16"></div>
								<div className="h-4 bg-gray-300 rounded w-20"></div>
								<div className="h-4 bg-gray-300 rounded w-14"></div>
							</div>
						) : (
							<div>
								<ul className="flex space-x-6">
									{menuItems.map(item => (
										<li key={item.id}>
											{item.url && item.url.startsWith('http') && !item.url.includes(window?.location?.hostname || '') ? (
												<a
													href={item.url}
													className="text-gray-700 hover:text-blue-600 transition-colors"
													target={item.target || '_blank'}
													rel="noopener noreferrer"
												>
													{item.title}
												</a>
											) : (
												<Link
													to={convertWordPressUrlToRoute(item.url)}
													className="text-gray-700 hover:text-blue-600 transition-colors"
												>
													{item.title}
												</Link>
											)}
										</li>
									))}
								</ul>
								{!hasMenuSupport && (
									{ /* On affiche un message pour indiquer que les menus ne sont pas disponibles (on utilise le fallback)*/ }
									<p className="text-xs text-gray-500 mt-1">Menu de démonstration</p>
								)}
							</div>
						)}
					</nav>
				</div>
			</div>
		</header>
	);
}

2.3 Créer un composant Footer

Pour le footer, c’est la même histoire que pour le header. On récupère le menu de navigation qui correspond au menu de pied de page de votre site et on crée un fallback si les menus ne sont pas disponibles.

// app/components/Footer.jsx
import { Link } from 'react-router';
import { useState, useEffect } from 'react';

const API_URL = import.meta.env.VITE_WP_API_URL;

export default function Footer() {
	const [menuItems, setMenuItems] = useState([]);

	useEffect(() => {
		async function fetchFooterMenu() {
			try {
				const response = await fetch(`${API_URL}/menus/footer-menu`);
				if (response.ok) {
					const items = await response.json();
					setMenuItems(items);
				}
			} catch (error) {
				console.error('Error fetching footer menu:', error);
			}
		}

		fetchFooterMenu();
	}, []);

	{ /* Puisque l'on utilise un code similaire dans le header on pourrait créer un helper, voir utils/convertWordPressUrlToRoute.ts */ }
	const convertWordPressUrlToRoute = (url) => {
		if (!url) return '/';

		try {
			const urlObj = new URL(url);
			let path = urlObj.pathname;
			path = path.replace(/\/$/, '') || '/';
			return path;
		} catch (error) {
			return url;
		}
	};

	return (
		<footer className="bg-gray-800 text-white mt-12">
			<div className="max-w-4xl mx-auto px-4 py-8">
				<div className="flex flex-col md:flex-row justify-between items-center">
					<div className="mb-4 md:mb-0">
						<p>&copy; 2025 Mon Site WordPress. Tous droits réservés.</p>
					</div>

					{menuItems.length > 0 && (
						<nav>
							<ul className="flex space-x-6">
								{menuItems.map(item => (
									<li key={item.id}>
										{item.url && item.url.startsWith('http') && !item.url.includes(window?.location?.hostname || '') ? (
											<a
												href={item.url}
												className="text-gray-300 hover:text-white transition-colors"
												target={item.target || '_blank'}
												rel="noopener noreferrer"
											>
												{item.title}
											</a>
										) : (
											<Link
												to={convertWordPressUrlToRoute(item.url)}
												className="text-gray-300 hover:text-white transition-colors"
											>
												{item.title}
											</Link>
										)}
									</li>
								))}
							</ul>
						</nav>
					)}
				</div>
			</div>
		</footer>
	);
}

2.4 Intégrer Header et Footer dans le layout

Maintenant que nous avons nos composants Header et Footer, nous pouvons les intégrer dans notre layout.

// app/root.tsx
import { Outlet, Meta, Scripts } from "react-router";
import Header from "./components/Header";
import Footer from "./components/Footer";
import "./app.css";

{ /* Vous pouvez gérer les méta-données pour le SEO */ }
export function meta() {
	return [
		{ charset: "utf-8" },
		{ name: "viewport", content: "width=device-width, initial-scale=1" },
		{ title: "WordPress React Front" },
		{ name: "description", content: "WordPress React Frontend" }
	];
}

export default function Root() {
	return (
		<html lang="fr">
			<head>
				<Meta />
			</head>
			<body className="bg-gray-50 text-gray-900 min-h-screen flex flex-col">
				<Header />
				<main className="flex-grow">
					<div className="max-w-4xl mx-auto px-4 py-8">
						<Outlet />
					</div>
				</main>
				<Footer />
				<Scripts />
			</body>
		</html>
	);
}

3. Gestion des pages WordPress

Maintenant que nous gérons les articles et les menus, nous pouvons nous attaquer aux pages WordPress. Les pages sont différentes des articles : elles sont généralement statiques et organisées hiérarchiquement, il n’y a donc pas de pagination.

3.1 Créer la route pour les pages

Comme nous l’avons fait précédemment, nous allons créer une route pour les pages. Cette route sera similaire à la route pour les articles, mais elle prendra un paramètre slug qui correspond au slug de la page.

// app/routes/$slug.tsx
import Page from "../components/Page";
import type { LoaderFunctionArgs } from "react-router";

const API_URL = import.meta.env.VITE_WP_API_URL;

export async function loader({ params }: LoaderFunctionArgs) {
	try {
		const { slug } = params;

		// On récupère la page par son slug
		const response = await fetch(`${API_URL}/pages?slug=${slug}&_embed`);

		if (!response.ok) {
			throw new Error(`API responded with status: ${response.status}`);
		}

		const pages = await response.json();

		// L'API retourne un tableau, même pour un seul élément (on récupère le premier)
		if (pages.length === 0) {
			throw new Error('Page not found');
		}

		const page = pages[0];
		return { page };
	} catch (error) {
		console.error("Error fetching page from server:", error);
		return {
			page: null,
			error: error instanceof Error ? error.message : "Unknown error"
		};
	}
}

export default function PageDetail() {
	return <Page />;
}

3.2 Créer le composant Page

Notre application est désormais prête à afficher les pages de WordPress.

// app/components/Page.jsx
import { useLoaderData } from 'react-router';

export default function Page() {
	const { page, error } = useLoaderData();

	if (error) {
		return (
			<div className="text-center py-12">
				<h1 className="text-3xl font-bold text-gray-900 mb-4">Page non trouvée</h1>
				<p className="text-gray-600 mb-8">
					Désolé, la page que vous recherchez n'existe pas.
				</p>
				<p className="text-red-600">Erreur: {error}</p>
			</div>
		);
	}

	if (!page) {
		return (
			<div className="text-center py-12">
				<div className="animate-pulse">
					<div className="h-8 bg-gray-300 rounded w-3/4 mx-auto mb-4"></div>
					<div className="h-4 bg-gray-300 rounded w-full mb-2"></div>
					<div className="h-4 bg-gray-300 rounded w-5/6 mx-auto"></div>
				</div>
			</div>
		);
	}

	return (
		<article className="prose prose-lg max-w-none">
			<h1 className="text-4xl font-bold text-gray-900 mb-6">
				{page.title.rendered} {/* On affiche le titre de la page en utilisant rendered */}
			</h1>

			{page.featured_media && page._embedded?.['wp:featuredmedia'] && (
				<div className="mb-8">
					<img
						src={page._embedded['wp:featuredmedia'][0].source_url}
						alt={page._embedded['wp:featuredmedia'][0].alt_text || page.title.rendered}
						className="w-full h-auto rounded-lg shadow-md"
					/>
				</div>
			)}

			<div
				className="prose prose-lg max-w-none wp-content"
				dangerouslySetInnerHTML={{ __html: page.content.rendered }}
				style={{
					// On reset les styles WordPress pour éviter les conflits
					color: 'inherit',
					fontFamily: 'inherit',
					fontSize: 'inherit',
					lineHeight: 'inherit'
				}}
			/>

			{page.modified && (
				<footer className="mt-8 pt-4 border-t border-gray-200 text-sm text-gray-500">
					Dernière modification : {new Date(page.modified).toLocaleDateString('fr-FR')}
				</footer>
			)}
		</article>
	);
}

3.3 Gestion des conflits de style

Lorsque vous affichez du contenu WordPress externe, vous pourriez rencontrer des conflits de style, c’est-à-dire que l’éditeur de WordPress va inclure des styles CSS qui pourraient interférer avec le design de votre application. On peut donc isoler le contenu WordPress en ajoutant des règles CSS pour isoler le contenu WordPress :

/* app/app.css */
/* WordPress content styling isolation */
.wp-content {
  /* Reset potential WordPress styling conflicts */
  & * {
    box-sizing: border-box;
  }

  /* Override any external fonts that might be loaded */
  & p, & h1, & h2, & h3, & h4, & h5, & h6, & div, & span {
    font-family: inherit !important;
    color: inherit !important;
  }

  /* Reset any external colors */
  & a {
    @apply text-blue-600 hover:text-blue-800;
  }

  /* Ensure images are responsive */
  & img {
    @apply max-w-full h-auto;
  }
}

Cette approche permet de :

  • Neutraliser les styles CSS externes qui pourraient être injectés dans le contenu
  • Forcer l’utilisation de vos propres fonts et couleurs pour maintenir la cohérence
  • Maintenir la responsivité des images et autres éléments

4. Recapitulatif

4.1 Pages, header, footer et pagination

Nous venons de voir comment gérer la pagination, comment afficher les pages WordPress et comment afficher le header et le footer. Nous avons aussi modifié notre fichier de variable d’environnement, il faut donc redémarrer l’application.

4.2 Test de l’application

Lancez le serveur de développement : npm run dev

L’application sera accessible sur http://localhost:5173. Vous devriez voir :

  1. Page d’accueil (/) : Liste des 5 premiers articles avec pagination
  2. Pages numérotées (/page/2, /page/3, etc.) : Articles des pages suivantes
  3. Articles (/post/123) : Détail d’un article spécifique
  4. Pages (/ma-page) : Contenu des pages statiques
  5. Header et Footer : Navigation avec menus (si configurés dans WordPress)

4.3 Structure de fichiers finale

Vous devriez vous retrouver avec la structure suivante :

app/
├── components/
│   ├── Footer.jsx           # Menu footer
│   ├── Header.jsx           # Menu header avec navigation
│   ├── Page.jsx            # Affichage des pages WordPress
│   ├── Pagination.jsx      # Navigation entre les pages
│   ├── Post.jsx            # Détail d'un article (existant)
│   └── PostsList.jsx       # Liste paginée des articles
├── routes/
│   ├── $slug.tsx           # Pages WordPress par slug
│   ├── _index.tsx          # Page d'accueil avec pagination
│   ├── page.$pageNumber.tsx # Pages numérotées
│   └── post.$id.tsx        # Articles individuels (existant)
├── utils/
│   └── apiHelpers.ts       # Fonctions d'aide pour l'API
├── root.tsx               # Layout avec Header/Footer
└── routes.ts              # Configuration des routes

Conclusion

Bon, c’était un peu long, mais on y est arrivé ! Dans ce deuxième article, nous avons considérablement enrichi notre application React headless :

  • Pagination complète avec navigation entre les pages
  • Menus dynamiques récupérés depuis WordPress et exposés via l’API
  • Support des pages en plus des articles
  • Structure modulaire avec composants réutilisables (pagination)

Notre application commence à ressembler à un vrai site web ! Dans le prochain article de cette série, nous verrons comment optimiser les performances avec le cache, implémenter la recherche, gérer les 404 et améliorer le SEO.

Un projet digital à réaliser ?