[Nom de la Fonctionnalité Visual Editing]
[Nom de la Fonctionnalité Visual Editing]
Documentation technique
Type de ressource : Mécanisme de prévisualisation / Visual Editing
Fichier source :sanity-studio/[fichier-concerne].tsou configuration associée
Statut : [Actif / Déprécié / En développement]
Slice : [Numéro de la tranche verticale]
Version : 1.0
1. Vue d’ensemble (DOC)
1.1. Description
[Description concise de la fonctionnalité de prévisualisation ou d’édition visuelle]
1.2. Objectif
- [Objectif principal pour les contributeurs]
- [Bénéfice UX ou workflow]
1.3. Périmètre concerné1/2
| Type de contenu | Statut |
|---|---|
| post | [✅/❌] |
| project | [✅/❌] |
| page | [✅/❌] |
| author | [✅/❌] |
| category | [✅/❌] |
1.3. Périmètre concerné2/2
| Environnement | Statut |
|---|---|
| Development | [✅/❌] |
| Staging | [✅/❌] |
| Production | [✅/❌] |
2. Architecture et flux (DOC)
2.1. Diagramme de flux
[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]2.2. Composants impliqués
| Composant | Rôle | Couche |
|---|---|---|
| Sanity Studio | Déclenchement de la preview | L1 (CMS) |
| Middleware Cloudflare | Validation et pose du cookie | L3 (Backend) |
| Astro | Détection et rendu du draft | L2 (Frontend) |
| getSanityClient() | Configuration conditionnelle | L2 (Frontend) |
3. Configuration (CODE)
3.1. Variables d’environnement requises
| 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) | - |
3.2. Configuration Sanity Studio
// sanity-studio/sanity.config.ts ou fichier dédiéimport { defineConfig } from 'sanity';
export default defineConfig({ // ... configuration de base document: { productionUrl: async (prev, { document, getClient }) => { 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)
// 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;}3.4. Configuration Astro (L2)
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, });}3.5. Route de sortie du mode preview
export const prerender = false;
Astro.cookies.delete('sanity-preview', { path: '/' });return Astro.redirect('/');4. Contrats d’Entrée/Sortie (SPEC)
4.1. Inputs (activation preview)
| Paramètre | Type | Source | Validation |
|---|---|---|---|
secret |
string |
Query parameter | Comparaison à temps constant |
slug |
string |
Query parameter | Validation anti-open-redirect |
4.2. Outputs (cookie + redirection)
| Élément | Type | Description |
|---|---|---|
Cookie sanity-preview |
string |
Valeur true, durée 1 heure |
| Redirection | 307 |
Redirection vers le slug demandé |
4.3. États du mode preview
| État | Condition | Comportement |
|---|---|---|
| Activé | Cookie présent | Client Sanity en mode previewDrafts |
| Désactivé | Cookie absent | Client Sanity en mode published |
| Erreur | Secret invalide | Retour 401 Unauthorized |
5. Tests attendus (TEST)
5.1. Tests unitaires
| Scénario | Description | Résultat attendu |
|---|---|---|
| Validation du secret | Secret correct vs incorrect | 307 Redirect vs 401 Unauthorized |
| Protection anti-open-redirect | Slug malformé | Redirection vers / |
| Pose du cookie | Activation preview | Cookie sanity-preview=true posé |
5.2. Tests d’intégration
| Scénario | Dépendances | Description | Résultat attendu |
|---|---|---|---|
| Flux complet | Studio + Middleware + Astro | Activation → Cookie → Draft affiché | Contenu draft visible |
| Sortie du mode | Route exit-preview | Suppression du cookie | Retour au mode published |
5.3. Tests E2E
| Scénario | Parcours utilisateur | Description | Résultat attendu |
|---|---|---|---|
| Preview depuis Studio | Sanity Studio → Preview → Site | Cliquer sur “Open Preview” | Contenu draft affiché dans le vrai design |
6. Intégration (INTÉG)
6.1. Intégration avec les composants
| Composant | Type | Rôle | Méthode d’intégration |
|---|---|---|---|
| Middleware Cloudflare | L3 | Validation et pose du cookie | Route /api/preview |
| Astro | L2 | Détection et rendu | getSanityClient() conditionnel |
| Sanity Studio | L1 | Déclenchement | Bouton “Open Preview” |
6.2. Points d’attention
- Le secret de preview ne doit jamais être exposé côté client
- Le token de lecture des drafts est uniquement disponible en runtime
- La documentation Starlight n’est pas concernée par ce mécanisme
6.3. Vérification de cohérence
- Le secret est identique dans Cloudflare et Sanity
- Le cookie est posé correctement
- Le client Sanity bascule en mode previewDrafts
- Les tests E2E passent
7. Sécurité
7.1. Mécanismes de sécurité
| 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 |
7.2. Tokens et permissions
| 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) |
7.3. Règles de sécurité
- ✅ Le secret de preview n’est jamais exposé côté client
- ✅ Le token de lecture des drafts est uniquement disponible en runtime
- ✅ Aucune page en mode preview n’est générée en SSG
- ✅ Les webhooks de rebuild ne sont déclenchés que sur
publish
8. Performance et limites
8.1. Considérations de performance
| Mode | useCdn |
Cache Edge | Rendu |
|---|---|---|---|
| Production | true |
Activé | SSG |
| Preview | false |
Désactivé | SSR |
8.2. Limitations
- ❌ Le mode preview désactive le CDN Sanity (impact sur les performances)
- ❌ Le mode preview force le bypass du cache Edge Cloudflare
- ❌ Les pages en preview sont rendues en SSR (plus lent que SSG)
9. Débogage et troubleshooting
9.1. Problèmes courants
| Symptôme | Cause probable | Solution |
|---|---|---|
| “Jeton de prévisualisation invalide” | Secret incorrect ou manquant | Vérifier SANITY_PREVIEW_SECRET |
| Page blanche en preview | Token de lecture des drafts manquant | Vérifier SANITY_API_READ_TOKEN |
| 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 |
9.2. Commandes de debug
# Test de validation du secretcurl -v "https://votresite.com/api/preview?secret=INVALID&slug=/"# Attendu : 401 Unauthorized
# Test avec secret validecurl -v "https://votresite.com/api/preview?secret=VALID_SECRET&slug=/blog/test"# Attendu : 307 Redirect + Set-Cookie10. Bonnes pratiques
10.1. Pour les développeurs
- ✅ 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 staging avant production
10.2. Pour les contributeurs
- ✅ 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)
11. Évolutions et roadmap
11.1. Améliorations planifiées
- [Évolution 1 : ex. Intégration de Sanity Visual Editing (overlays)]
- [Évolution 2 : ex. Support du mode preview pour la documentation Starlight]
11.2. Limitations connues
- ❌ 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
12. Ressources liées
- 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
13. Historique des modifications
| Date | Version | Description | Auteur |
|---|---|---|---|
| YYYY-MM-DD | 1.0 | Création initiale | [Nom] |