BaliseTonSite

Connecter Next.js à WordPress

Le front rencontre le back. C'est ici que ça devient concret.

6.1Variables d'environnement

Première étape : configurer l'URL de ton WordPress dans un fichier .env.local :

Bashbash
# .env.local (à la racine de ton projet Next.js)
WP_API_URL=http://mon-cms-headless.local/wp-json

# En production, ce sera :
# WP_API_URL=https://cms.monsite.fr/wp-json

6.2Typer les réponses WordPress

Avant de fetcher, on définit les types TypeScript pour les données WordPress :

TypeScripttypescript
// lib/types/wordpress.ts

export interface WPPost {
  id: number
  date: string
  slug: string
  title: { rendered: string }
  content: { rendered: string }
  excerpt: { rendered: string }
  featured_media: number
  categories: number[]
  _embedded?: {
    'wp:featuredmedia'?: Array<{
      source_url: string
      alt_text: string
    }>
  }
}

export interface WPProjet {
  id: number
  title: string
  slug: string
  excerpt: string
  image: string | null
  url_site: string | null
  repo_github: string | null
  client: string
  date_livraison: string
  stack: string[]
}

export interface WPPage {
  id: number
  slug: string
  title: { rendered: string }
  content: { rendered: string }
}

6.3Fonction fetch générique

Crée une fonction utilitaire pour appeler l'API WordPress :

TypeScripttypescript
// lib/wordpress.ts

const API_URL = process.env.WP_API_URL

if (!API_URL) {
  throw new Error('WP_API_URL non configurée dans .env.local')
}

export async function fetchWP<T>(
  endpoint: string,
  options?: RequestInit
): Promise<T> {
  const url = `${API_URL}${endpoint}`

  const res = await fetch(url, {
    ...options,
    headers: {
      'Content-Type': 'application/json',
      ...options?.headers,
    },
    next: { revalidate: 60 },  // ISR : revalide toutes les 60 secondes
  })

  if (!res.ok) {
    throw new Error(`WordPress API error: ${res.status} on ${endpoint}`)
  }

  return res.json()
}

// Fonctions spécialisées
export async function getPosts(perPage = 10) {
  return fetchWP<WPPost[]>(
    `/wp/v2/posts?per_page=${perPage}&_embed`
  )
}

export async function getPostBySlug(slug: string) {
  const posts = await fetchWP<WPPost[]>(
    `/wp/v2/posts?slug=${encodeURIComponent(slug)}&_embed`
  )
  return posts[0] ?? null
}

export async function getProjets() {
  return fetchWP<WPProjet[]>('/monsite/v1/projets')
}

export async function getProjetBySlug(slug: string) {
  return fetchWP<WPProjet>(
    `/monsite/v1/projets/${encodeURIComponent(slug)}`
  )
}

6.4Afficher les données dans un Server Component

La magie de Next.js : tes composants sont des Server Components par défaut. Tu peux fetcher directement dans le composant :

TSXtsx
// app/projets/page.tsx

import { getProjets } from '@/lib/wordpress'

export default async function ProjetsPage() {
  const projets = await getProjets()

  return (
    <main className="max-w-6xl mx-auto px-4 py-12">
      <h1 className="text-4xl font-bold mb-8">Nos projets</h1>

      <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
        {projets.map((projet) => (
          <article
            key={projet.id}
            className="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden"
          >
            {projet.image && (
              <img
                src={projet.image}
                alt={projet.title}
                className="w-full h-48 object-cover"
              />
            )}
            <div className="p-5">
              <h2 className="text-lg font-bold mb-2">{projet.title}</h2>
              <p className="text-slate-600 text-sm mb-3">{projet.excerpt}</p>
              <div className="flex flex-wrap gap-2">
                {projet.stack.map((tech) => (
                  <span
                    key={tech}
                    className="px-2 py-1 bg-blue-50 text-blue-600 text-xs
                               rounded-full font-medium"
                  >
                    {tech}
                  </span>
                ))}
              </div>
            </div>
          </article>
        ))}
      </div>
    </main>
  )
}

Pas de useEffect, pas de useState, pas de loading spinner. Le contenu est récupéré côté serveur et envoyé en HTML au navigateur. SEO parfait, chargement instantané.

6.5Page dynamique avec slug

TSXtsx
// app/projets/[slug]/page.tsx

import { getProjetBySlug, getProjets } from '@/lib/wordpress'
import { notFound } from 'next/navigation'

// Génère les pages statiques au build
export async function generateStaticParams() {
  const projets = await getProjets()
  return projets.map((p) => ({ slug: p.slug }))
}

export default async function ProjetPage({
  params,
}: {
  params: Promise<{ slug: string }>
}) {
  const { slug } = await params
  const projet = await getProjetBySlug(slug)

  if (!projet) return notFound()

  return (
    <main className="max-w-4xl mx-auto px-4 py-12">
      <h1 className="text-4xl font-bold mb-4">{projet.title}</h1>
      <p className="text-slate-500 mb-8">
        Client : {projet.client} - Livré le {projet.date_livraison}
      </p>

      {projet.image && (
        <img
          src={projet.image}
          alt={projet.title}
          className="w-full rounded-xl mb-8"
        />
      )}

      <div
        className="prose max-w-none"
        dangerouslySetInnerHTML={{ __html: projet.content }}
      />
    </main>
  )
}

Verifie tes acquis

5 questions pour valider ce chapitre

1. Pourquoi ne faut-il PAS utiliser NEXT_PUBLIC_ pour l'URL WordPress ?

Valide et sauvegarde ce chapitre

Ne perds pas le fil de ton apprentissage. Chaque QCM terminé sauvegarde ton score. Crée ton profil gratuitement pour débloquer toutes les évaluations du site et retrouver tes résultats plus tard.

Commencer l'aventure
Déjà membre ?Connecte-toi