Skip to content

[Nom de la Fonctionnalité Visual Editing]

Type de ressource : Mécanisme de prévisualisation / Visual Editing
Fichier source : sanity-studio/[fichier-concerne].ts ou configuration associée
Statut : [Actif / Déprécié / En développement]

[Description concise de la fonctionnalité de prévisualisation ou d’édition visuelle]

  • [Objectif principal pour les contributeurs]
  • [Bénéfice UX ou workflow]
  • Types de contenu : [Liste des schémas Sanity concernés : post, project, page, etc.]
  • Environnements : [Development / Staging / Production]
  • Rôles utilisateurs : [Administrator, Editor, Contributor, Viewer]
[Sanity Studio]
│
▼
[Action utilisateur : ex. "Open Preview"]
│
▼
[Cloudflare Middleware Worker (L3)]
│ • Validation du secret
│ • Pose du cookie de preview
│ • Redirection
▼
[Astro (L2)]
│ • Détection du cookie/header
│ • Configuration du client Sanity
│ • Rendu SSR
▼
[Affichage du contenu draft]
Composant Rôle Couche
[Nom du composant] [Description du rôle] [L1 / L2 / L3]
[Nom du composant] [Description du rôle] [L1 / L2 / L3]
Variable Description Obligatoire Valeur par défaut
SANITY_PREVIEW_SECRET Secret de validation de la preview ✅ -
SANITY_API_READ_TOKEN Token API avec droits de lecture des drafts ✅ (en preview) -
[Autre variable] [Description] [✅/❌] [Valeur]
// sanity-studio/sanity.config.ts ou fichier dédié
import { defineConfig } from 'sanity';
import { structureTool } from 'sanity/structure';
export default defineConfig({
// ... configuration de base
plugins: [
// Plugins de visual editing si applicable
],
document: {
productionUrl: async (prev, { document, getClient }) => {
// Logique de génération de l'URL de preview
const secret = process.env.SANITY_PREVIEW_SECRET;
const slug = document.slug?.current || '/';
return `${process.env.SITE_URL}/api/preview?secret=${secret}&slug=${slug}`;
},
},
});

3.3. Configuration Middleware Cloudflare (L3)

Section titled “3.3. Configuration Middleware Cloudflare (L3)”
// services/middleware/src/index.ts (extrait pertinent)
if (url.pathname === '/api/preview') {
const secret = url.searchParams.get('secret');
const rawSlug = url.searchParams.get('slug') || '/';
// Validation à temps constant
if (!secret || secret !== env.SANITY_PREVIEW_SECRET) {
return new Response('Jeton invalide', { status: 401 });
}
// Protection anti-open-redirect
const sanitizedSlug = (rawSlug.startsWith('/') && !rawSlug.startsWith('//'))
? rawSlug
: '/';
const response = Response.redirect(new URL(sanitizedSlug, url.origin).toString(), 307);
// Cookie de preview sécurisé
response.headers.append(
'Set-Cookie',
`sanity-preview=true; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=3600`
);
return response;
}
src/lib/sanity.ts
import { createClient } from '@sanity/client';
import type { AstroGlobal } from 'astro';
export function getSanityClient(astro: AstroGlobal) {
const isPreview =
astro.cookies.get('sanity-preview')?.value === 'true' ||
astro.request.headers.get('x-sanity-preview') === 'true';
return createClient({
projectId: import.meta.env.PUBLIC_SANITY_PROJECT_ID,
dataset: import.meta.env.PUBLIC_SANITY_DATASET,
apiVersion: '2025-01-01',
useCdn: !isPreview, // Désactive le CDN en preview
perspective: isPreview ? 'previewDrafts' : 'published',
token: isPreview
? import.meta.env.SANITY_API_READ_TOKEN
: undefined,
});
}
Mécanisme Implémentation Rationale
Validation du secret Comparaison à temps constant Évite les attaques temporelles
Cookie sécurisé HttpOnly, Secure, SameSite=Lax Protection XSS et CSRF
Durée de vie limitée Max-Age=3600 (1 heure) Limite la fenêtre d’exposition
Anti-open-redirect Validation stricte du slug Évite les redirections malveillantes
Bypass cache Edge cf.cacheEverything = false Garantit le contenu draft frais
Token Permissions Stockage
SANITY_PREVIEW_SECRET Validation de la requête de preview Cloudflare Secrets + Sanity env
SANITY_API_READ_TOKEN Lecture des drafts uniquement Cloudflare Secrets (runtime uniquement)
  • ✅ Le secret de preview n’est jamais exposé côté client
  • ✅ Le token de lecture des drafts est uniquement disponible en runtime (Pages Functions)
  • ✅ Aucune page en mode preview n’est générée en SSG
  • ✅ La documentation Starlight n’est pas concernée par ce mécanisme
  • ✅ Les webhooks de rebuild ne sont déclenchés que sur publish (jamais sur les drafts)
  1. Création/modification : Le contributeur édite un document dans Sanity Studio
  2. Sauvegarde : Le document est sauvegardé en draft (statut draft)
  3. Prévisualisation : Le contributeur clique sur “Open Preview” dans le Studio
  4. Validation : Le Middleware Cloudflare valide le secret et pose le cookie
  5. Affichage : Astro détecte le cookie et affiche le contenu draft
  6. Publication : Une fois validé, le contributeur publie le document
  7. Rebuild : Le webhook déclenche un nouveau build SSG
src/pages/api/exit-preview.ts
export const prerender = false;
Astro.cookies.delete('sanity-preview', { path: '/' });
return Astro.redirect('/');

Usage : https://votresite.com/api/exit-preview

Type de contenu URL de preview Composant de rendu
Article de blog /api/preview?slug=/blog/[slug] src/pages/blog/[slug].astro
Projet portfolio /api/preview?slug=/portfolio/[slug] src/pages/portfolio/[slug].astro
Page vitrine /api/preview?slug=/[slug] src/pages/[slug].astro
Symptôme Cause probable Solution
“Jeton de prévisualisation invalide” Secret incorrect ou manquant Vérifier SANITY_PREVIEW_SECRET dans Cloudflare et Sanity
Page blanche en preview Token de lecture des drafts manquant Vérifier SANITY_API_READ_TOKEN dans Cloudflare Secrets
Contenu publié affiché au lieu du draft Cookie non détecté Vérifier que le navigateur accepte les cookies
Redirection infinie Slug malformé ou open-redirect bloqué Vérifier le format du slug (doit commencer par /)
Contenu en cache Bypass cache non actif Vérifier que le Middleware force cacheEverything: false
// Logs à vérifier en cas de problème
// 1. Cloudflare Worker Logs (Middleware)
console.log('Preview activation attempt', { secret, slug });
// 2. Astro Logs (runtime)
console.log('Preview mode detected', { isPreview, cookie });
// 3. Sanity Studio Console
// Vérifier les erreurs dans la console du navigateur
Terminal window
# Test de validation du secret
curl -v "https://votresite.com/api/preview?secret=INVALID&slug=/"
# Attendu : 401 Unauthorized
# Test avec secret valide
curl -v "https://votresite.com/api/preview?secret=VALID_SECRET&slug=/blog/test"
# Attendu : 307 Redirect + Set-Cookie
  • ✅ Toujours utiliser la comparaison à temps constant pour valider les secrets
  • ✅ Ne jamais logger les secrets ou tokens dans les logs
  • ✅ Tester le mécanisme de preview en environnement de staging avant production
  • ✅ Documenter les changements de configuration dans ce fichier
  • ✅ Utiliser la preview systématiquement avant publication
  • ✅ Vérifier le rendu sur différents appareils (responsive)
  • ✅ Ne pas partager l’URL de preview (elle contient le secret)
  • ✅ Se déconnecter de la preview après utilisation (/api/exit-preview)
  • ⚠️ Le mode preview désactive le CDN Sanity (useCdn: false)
  • ⚠️ Le mode preview force le bypass du cache Edge Cloudflare
  • ⚠️ Les pages en preview sont rendues en SSR (plus lent que SSG)
  • ✅ Limiter la durée de vie du cookie (1 heure par défaut)
  • [Évolution 1 : ex. Intégration de Sanity Visual Editing (overlays)]
  • [Évolution 2 : ex. Support du mode preview pour la documentation Starlight]
  • [Évolution 3 : ex. Prévisualisation multi-appareils synchronisée]
  • ❌ La documentation Starlight n’est pas compatible avec ce mécanisme (Git-based)
  • ❌ Le mode preview ne fonctionne pas avec le SSG (SSR uniquement)
  • ❌ Les médias uploadés en draft ne sont pas accessibles publiquement avant publication
  • Documentation Sanity : Preview Content
  • Documentation Astro : Draft Mode
  • DAT L1 : §4.7 - Stratégie de Preview (Draft Mode)
  • DAT L2 : §5.3 - Gestion du mode Preview
  • DAT L3 : §3.1 - Middleware Global (validation du secret)
  • Fichiers concernés :
    • services/middleware/src/index.ts
    • src/lib/sanity.ts
    • src/pages/api/exit-preview.ts
    • sanity-studio/sanity.config.ts
Date Version Description Auteur
YYYY-MM-DD 1.0 Création initiale [Nom]
YYYY-MM-DD 1.1 [Modification] [Nom]
Edit page

Last updated: