Chapitre 9.6 — Previews & brouillons en headless
⏱️ TL;DR — En classique, un rédacteur clique « Aperçu » et voit son brouillon. En headless, le front (Next.js statique) n’affiche que le publié : il faut un mode preview dédié. Le pattern : un rédacteur ouvre un lien de preview (émis par WordPress, signé) → Next.js active le Draft Mode → il fetch le contenu non publié en s’authentifiant côté serveur (Application Password/JWT, ch. 9.2) → rend la page à la volée (pas de cache). C’est le chaînon qui rend le headless acceptable pour les rédacteurs.
🎯 Objectifs
- Comprendre pourquoi la preview est un problème spécifique au headless.
- Mettre en place le Draft Mode de Next.js.
- Fetcher un brouillon authentifié côté serveur.
- Sécuriser le lien de preview.
1. Le problème
Un site statique (SSG/ISR) ne connaît que le contenu publié (status=publish). Un rédacteur qui veut prévisualiser un brouillon ou une modification non publiée ne peut pas le voir sur le front statique. Sans solution de preview, le headless est rejeté par les équipes éditoriales.
2. Le Draft Mode de Next.js
Next.js fournit le Draft Mode : un cookie qui fait basculer le rendu en dynamique (contourne le cache statique) pour la session de preview.
// app/api/preview/route.ts
import { draftMode } from 'next/headers';
import { redirect } from 'next/navigation';
import { NextRequest } from 'next/server';
export async function GET(req: NextRequest) {
const params = req.nextUrl.searchParams;
const secret = params.get('secret');
const slug = params.get('slug');
if (secret !== process.env.PREVIEW_SECRET || !slug) { // valider le lien
return new Response('Invalid preview', { status: 401 });
}
(await draftMode()).enable(); // activer le Draft Mode
redirect(`/livres/${slug}`); // vers la page à prévisualiser
}Quand le Draft Mode est actif, votre page fetch le contenu non publié au lieu du cache.
3. Fetcher le brouillon (authentifié, côté serveur)
Dans le fetch, on détecte le Draft Mode et on demande le contenu avec authentification (les brouillons exigent l’auth, ch. 9.2) :
// lib/wp.ts
import { draftMode } from 'next/headers';
export async function getLivre(slug: string) {
const { isEnabled } = await draftMode();
const base = process.env.WP_API_URL!;
const url = isEnabled
? `${base}/livre?slug=${slug}&status=draft,pending,publish&context=edit`
: `${base}/livre?slug=${slug}`;
const res = await fetch(url, {
// en preview : pas de cache + auth serveur
cache: isEnabled ? 'no-store' : 'force-cache',
headers: isEnabled
? { Authorization: 'Basic ' + Buffer.from(process.env.WP_APP_CREDS!).toString('base64') }
: {},
...(isEnabled ? {} : { next: { revalidate: 3600, tags: [`livre:${slug}`] } }),
});
const arr = await res.json();
return arr[0] ?? null;
}- En preview :
status=draft,pending,publish,context=edit,no-store(toujours frais), auth (Application Password) — côté serveur uniquement. - Hors preview : lecture publique + cache/ISR (ch. 9.5).
⚠️ Piège n°1 — l’auth de preview côté client. Les identifiants de preview (Application Password) doivent rester côté serveur (route handler / fetch serveur). Ne les exposez jamais au navigateur, même « juste pour la preview ». Le Draft Mode s’active par cookie ; le fetch authentifié se fait côté serveur.
4. Émettre le lien de preview depuis WordPress
On personnalise le lien « Aperçu » du CPT pour qu’il pointe vers le front Next :
add_filter( 'preview_post_link', function ( $link, $post ) {
if ( 'livre' !== $post->post_type ) { return $link; }
return add_query_arg(
array( 'secret' => PREVIEW_SECRET, 'slug' => $post->post_name ),
'https://app.exemple.com/api/preview'
);
}, 10, 2 );Le rédacteur clique Aperçu dans WordPress → arrive sur /api/preview → Draft Mode activé → voit son brouillon sur le front Next.
⚠️ Piège n°2 — le secret de preview dans l’URL. Le lien contient un secret (dans l’URL) : utilisez un secret fort, en HTTPS, et envisagez une expiration (jeton signé daté) plutôt qu’un secret statique partagé, pour limiter la fuite via l’historique/referer. Prévoyez aussi une sortie du mode preview (
draftMode().disable()).
5. Sortir du mode preview
// app/api/preview/disable/route.ts
import { draftMode } from 'next/headers';
export async function GET() {
(await draftMode()).disable();
return new Response('Preview désactivée');
}Prévoyez un moyen visible (bannière « Mode preview — quitter ») pour ne pas laisser un rédacteur bloqué en preview.
💡 Pour un dev React — C’est le Draft Mode standard de Next.js, appliqué à WordPress : un cookie bascule le rendu en dynamique, un route handler valide le lien, le fetch passe en
no-store+ auth serveur. Vous connaissez déjà ce mécanisme (souvent utilisé avec Sanity/Contentful) ; ici, la source est WordPress et l’auth est un Application Password. Aucune compétence nouvelle — juste le câblage à WordPress.
✏️ Exercices
- Pourquoi la preview est-elle un problème spécifique au headless ?
- Quel mécanisme Next.js utilise-t-on, et qu’active-t-il ?
- Où doivent vivre les identifiants utilisés pour fetcher le brouillon, et pourquoi ?
✅ Solution
- Parce que le front statique (SSG/ISR) ne connaît que le contenu publié ; un brouillon (non publié) n’existe pas dans les pages générées. Il faut un mode dédié pour rendre le brouillon à la volée, authentifié.
- Le Draft Mode de Next.js : un cookie qui fait basculer le rendu en dynamique (contourne le cache), permettant de fetcher le contenu non publié.
- Côté serveur (route handler / fetch serveur), jamais exposés au navigateur : les brouillons exigent une authentification (Application Password/JWT) qui ne doit pas fuiter côté client.
🧠 Quiz de révision
1. Pourquoi le headless casse-t-il la preview classique ?
Le front statique n’affiche que le publié ; le brouillon n’est pas dans les pages générées.
2. Quel outil Next.js gère la preview ?
Le Draft Mode (cookie qui rend la page en dynamique, hors cache).
3. Comment fetch-t-on un brouillon ?
Avec status=draft…, context=edit, no-store et une auth serveur (Application Password), côté serveur.
4. Comment WordPress dirige-t-il vers la preview du front ?
En filtrant preview_post_link pour pointer vers /api/preview (avec secret + slug).
5. Quel risque avec un secret de preview statique dans l’URL ?
Sa fuite (historique/referer) ; on préfère un jeton signé daté (expirable) et HTTPS.
Chapitre suivant : Arbitrages : headless ou pas ?.