Dossier d'Architecture Technique (DAT)
Couche 01 : Gestion et Stockage du Contenu
Section titled âCouche 01 : Gestion et Stockage du ContenuâProjet : RĂ©architecture du Site Web (Vitrine, Blog, Portfolio, Documentation)
Statut : Version Initiale
Date : 1er Septembre 2026
Ăquipe : AMN
1. Introduction & Objectifs
Section titled â1. Introduction & ObjectifsâCe document dĂ©crit les spĂ©cifications techniques de la Couche 01 (Contenu). Lâobjectif de cette couche est dâassurer le stockage, la modĂ©lisation, la sĂ©curisation et la mise Ă disposition des donnĂ©es textuelles et mĂ©dias du site web.
Cette couche est dĂ©couplĂ©e des couches de PrĂ©sentation (Couche 02) et de Logiciel/Backend (Couche 03) selon les principes dâune architecture Jamstack / Headless.
2. Architecture Conceptuelle de la Couche Contenu
Section titled â2. Architecture Conceptuelle de la Couche ContenuâLa couche contenu est hybride. Elle sĂ©pare le contenu dynamique (orientĂ© marketing) du contenu technique (orientĂ© produit/dĂ©veloppeur) :
âââââââââââââââââââââââââââââââââââââââââ â COUCHE 01 : CONTENU â âââââââââââââââââââââŹââââââââââââââââââââ â ââââââââââââââââââââââŽâââââââââââââââââââââ ⌠⌠[ DonnĂ©es Dynamiques ] [ DonnĂ©es Techniques ] Vitrine, Blog, Portfolio Documentation â â ⌠⌠âââââââââââââââââ âââââââââââââââââ â Sanity.io â â Fichiers Git â â (CMS Headless)â â (Markdown/MDX)â âââââââââŹââââââââ âââââââââŹââââââââ â â ââââââââââââââââââââââŹâââââââââââââââââââââ â (Phase de Build) ⌠âââââââââââââââââââââââââââââââââââââââââ â COUCHE 02 : PRĂSENTATION â â (GĂ©nĂ©rateur Astro) â âââââââââââââââââââââââââââââââââââââââââ
3. Spécifications Techniques par Composant
Section titled â3. SpĂ©cifications Techniques par Composantâ3.1. Composant CMS Headless : Sanity.io
Section titled â3.1. Composant CMS Headless : Sanity.ioâDĂ©diĂ© Ă la gestion du contenu pour la Vitrine, le Blog et le Portfolio.
- ModÚle de Données (Content Schema) : Déclaré sous forme de code (Schema-as-Code) en TypeScript au sein du sous-projet
sanity-studio. - Format de Stockage : JSON propriétaire orienté graphe (Sanity Content Lake).
- Gestion des Médias : Tous les fichiers médias (images, vidéos, PDF, documents) sont stockés exclusivement sur Cloudflare R2 (et Cloudflare Stream pour les vidéos). Sanity ne conserve que les métadonnées et les URLs pointant vers R2/Stream (voir §4.8).
- Interface dâĂ©dition (Sanity Studio) : DĂ©ployĂ©e de maniĂšre indĂ©pendante ou intĂ©grĂ©e en tant que route dâadministration. Elle offre une prĂ©visualisation en temps rĂ©el aux contributeurs (Draft Mode, voir §4.7).
3.2. Composant Fichiers Locaux : Astro Starlight
Section titled â3.2. Composant Fichiers Locaux : Astro StarlightâDĂ©diĂ© Ă la gestion de la documentation technique.
-
Format de Stockage : Fichiers locaux
.md(Markdown) et.mdx(composants interactifs dans le contenu). -
Arborescence cible :
src/âââ content/âââ docs/âââ index.mdxâââ guide/â âââ installation.mdâ âââ configuration.mdâââ api/âââ reference.md -
Cycle de Vie : Gestion exclusive via Git. Toute modification de la documentation nécessite une Pull Request et passe par le pipeline CI/CD standard du projet.
4. Modélisation et Schémas des Données (Sanity)
Section titled â4. ModĂ©lisation et SchĂ©mas des DonnĂ©es (Sanity)âAfin de garantir la robustesse du typage lors de la rĂ©cupĂ©ration dans Astro, les schĂ©mas Sanity sont dĂ©finis en Schema-as-Code (TypeScript). Voici la structure complĂšte et enrichie.
4.1. Schéma utilitaire : author
Section titled â4.1. SchĂ©ma utilitaire : authorâ{ name: 'author', title: 'Auteur', type: 'document', fields: [ { name: 'name', title: 'Nom complet', type: 'string', validation: Rule => Rule.required() }, { name: 'slug', title: 'Slug', type: 'slug', options: { source: 'name' } }, { name: 'image', title: 'Photo', type: 'image', options: { hotspot: true } }, { name: 'bio', title: 'Biographie', type: 'text' }, { name: 'role', title: 'RĂŽle / Fonction', type: 'string' }, { name: 'social', title: 'RĂ©seaux sociaux', type: 'object', fields: [ { name: 'twitter', type: 'url' }, { name: 'linkedin', type: 'url' }, { name: 'github', type: 'url' } ]} ]}4.2. SchĂ©ma utilitaire : category
Section titled â4.2. SchĂ©ma utilitaire : categoryâ{ name: 'category', title: 'CatĂ©gorie', type: 'document', fields: [ { name: 'title', title: 'Nom', type: 'string', validation: Rule => Rule.required() }, { name: 'slug', title: 'Slug', type: 'slug', options: { source: 'title' } }, { name: 'description', title: 'Description', type: 'text' } ]}4.3. SchĂ©ma : Blog (post)
Section titled â4.3. SchĂ©ma : Blog (post)â{ name: 'post', title: 'Article de blog', type: 'document', fields: [ { name: 'title', title: 'Titre', type: 'string', validation: Rule => Rule.required() }, { name: 'slug', title: 'Slug', type: 'slug', options: { source: 'title' }, validation: Rule => Rule.required() }, { name: 'publishedAt', title: 'Date de publication', type: 'datetime' }, { name: 'status', title: 'Statut', type: 'string', options: { list: [ { title: 'Brouillon', value: 'draft' }, { title: 'PubliĂ©', value: 'published' }, { title: 'ArchivĂ©', value: 'archived' } ] }, initialValue: 'draft' }, { name: 'mainImage', title: 'Image principale', type: 'image', options: { hotspot: true } }, { name: 'excerpt', title: 'Extrait / RĂ©sumĂ©', type: 'text', rows: 3 }, { name: 'body', title: 'Contenu', type: 'array', of: [{ type: 'block' }] }, { name: 'author', title: 'Auteur', type: 'reference', to: [{ type: 'author' }] }, { name: 'categories', title: 'CatĂ©gories', type: 'array', of: [{ type: 'reference', to: [{ type: 'category' }] }] }, { name: 'tags', title: 'Tags', type: 'array', of: [{ type: 'string' }] }, { name: 'seo', title: 'SEO', type: 'object', fields: [ { name: 'metaTitle', title: 'Meta Title', type: 'string' }, { name: 'metaDescription', title: 'Meta Description', type: 'text', rows: 3 }, { name: 'ogImage', title: 'Image Open Graph', type: 'image' } ]} ]}4.4. SchĂ©ma : Portfolio (project)
Section titled â4.4. SchĂ©ma : Portfolio (project)â{ name: 'project', title: 'Projet Portfolio', type: 'document', fields: [ { name: 'title', title: 'Titre du projet', type: 'string', validation: Rule => Rule.required() }, { name: 'slug', title: 'Slug', type: 'slug', options: { source: 'title' } }, { name: 'client', title: 'Client', type: 'string' }, { name: 'description', title: 'Description courte', type: 'text' }, { name: 'projectUrl', title: 'URL du projet', type: 'url' }, { name: 'technologies', title: 'Technologies', type: 'array', of: [{ type: 'string' }] }, { name: 'gallery', title: 'Galerie', type: 'array', of: [{ type: 'image', options: { hotspot: true } }] }, { name: 'featured', title: 'Projet mis en avant', type: 'boolean', initialValue: false }, { name: 'publishedAt', title: 'Date', type: 'datetime' }, { name: 'status', title: 'Statut', type: 'string', options: { list: [ { title: 'Brouillon', value: 'draft' }, { title: 'PubliĂ©', value: 'published' }, { title: 'ArchivĂ©', value: 'archived' } ] }, initialValue: 'draft' }, { name: 'seo', title: 'SEO', type: 'object', fields: [ { name: 'metaTitle', title: 'Meta Title', type: 'string' }, { name: 'metaDescription', title: 'Meta Description', type: 'text' }, { name: 'ogImage', title: 'Image Open Graph', type: 'image' } ]} ]}4.5. SchĂ©ma : Vitrine (page)
Section titled â4.5. SchĂ©ma : Vitrine (page)â{ name: 'page', title: 'Page Vitrine', type: 'document', fields: [ { name: 'title', title: 'Titre de la page', type: 'string', validation: Rule => Rule.required() }, { name: 'slug', title: 'Slug', type: 'slug', options: { source: 'title' }, validation: Rule => Rule.required() }, { name: 'status', title: 'Statut', type: 'string', options: { list: [ { title: 'Brouillon', value: 'draft' }, { title: 'PubliĂ©', value: 'published' } ] }, initialValue: 'draft' }, { name: 'pageBuilder', title: 'Constructeur de page', type: 'array', of: [ { type: 'hero' }, { type: 'features' }, { type: 'testimonials' }, { type: 'cta' }, { type: 'richText' }, { type: 'imageGallery' } ] }, { name: 'seo', title: 'SEO', type: 'object', fields: [ { name: 'metaTitle', title: 'Meta Title', type: 'string' }, { name: 'metaDescription', title: 'Meta Description', type: 'text' }, { name: 'ogImage', title: 'Image Open Graph', type: 'image' } ]} ]}4.6. SchĂ©mas des blocs PageBuilder
Section titled â4.6. SchĂ©mas des blocs PageBuilderâ4.6.1. Bloc hero
Section titled â4.6.1. Bloc heroâ{ name: 'hero', title: 'Hero', type: 'object', fields: [ { name: 'heading', title: 'Titre principal', type: 'string' }, { name: 'subheading', title: 'Sous-titre', type: 'text' }, { name: 'backgroundImage', title: 'Image de fond', type: 'image', options: { hotspot: true } }, { name: 'ctaLabel', title: 'LibellĂ© du bouton', type: 'string' }, { name: 'ctaLink', title: 'Lien du bouton', type: 'url' } ]}4.6.2. Bloc features
Section titled â4.6.2. Bloc featuresâ{ name: 'features', title: 'FonctionnalitĂ©s', type: 'object', fields: [ { name: 'title', title: 'Titre de la section', type: 'string' }, { name: 'items', title: 'Liste des fonctionnalitĂ©s', type: 'array', of: [{ type: 'object', fields: [ { name: 'title', title: 'Titre', type: 'string' }, { name: 'description', title: 'Description', type: 'text' }, { name: 'icon', title: 'IcĂŽne (nom ou image)', type: 'string' } ] }]} ]}4.6.3. Bloc testimonials
Section titled â4.6.3. Bloc testimonialsâ{ name: 'testimonials', title: 'TĂ©moignages', type: 'object', fields: [ { name: 'title', title: 'Titre de la section', type: 'string' }, { name: 'items', title: 'TĂ©moignages', type: 'array', of: [{ type: 'object', fields: [ { name: 'quote', title: 'Citation', type: 'text' }, { name: 'authorName', title: 'Nom de lâauteur', type: 'string' }, { name: 'authorRole', title: 'RĂŽle / Entreprise', type: 'string' }, { name: 'authorImage', title: 'Photo', type: 'image' } ] }]} ]}4.6.4. Bloc cta
Section titled â4.6.4. Bloc ctaâ{ name: 'cta', title: 'Call to Action', type: 'object', fields: [ { name: 'heading', title: 'Titre', type: 'string' }, { name: 'text', title: 'Texte dâaccompagnement', type: 'text' }, { name: 'buttonLabel', title: 'LibellĂ© du bouton', type: 'string' }, { name: 'buttonLink', title: 'Lien du bouton', type: 'url' }, { name: 'style', title: 'Style', type: 'string', options: { list: [ { title: 'Primaire', value: 'primary' }, { title: 'Secondaire', value: 'secondary' } ] }} ]}4.6.5. Bloc richText
Section titled â4.6.5. Bloc richTextâ{ name: 'richText', title: 'Texte riche', type: 'object', fields: [ { name: 'content', title: 'Contenu', type: 'array', of: [{ type: 'block' }] } ]}4.6.6. Bloc imageGallery
Section titled â4.6.6. Bloc imageGalleryâ{ name: 'imageGallery', title: 'Galerie dâimages', type: 'object', fields: [ { name: 'title', title: 'Titre de la galerie', type: 'string' }, { name: 'images', title: 'Images', type: 'array', of: [{ type: 'image', options: { hotspot: true }, fields: [ { name: 'alt', title: 'Texte alternatif', type: 'string' }, { name: 'caption', title: 'LĂ©gende', type: 'string' } ] }]} ]}4.7. StratĂ©gie de Preview (Draft Mode)
Section titled â4.7. StratĂ©gie de Preview (Draft Mode)âLa prĂ©visualisation des contenus en brouillon (draft) est un besoin critique pour les contributeurs marketing. La stratĂ©gie retenue respecte strictement le dĂ©couplage dĂ©cidĂ© en Couche 03 : toute la logique dâactivation et de validation de la preview est portĂ©e par le Middleware Cloudflare indĂ©pendant, et non par Astro.
4.7.1. Principes
Section titled â4.7.1. Principesâ- Activation via un secret token passĂ© en query parameter depuis Sanity Studio.
- Validation et pose du cookie de preview effectuées exclusivement par le Cloudflare Middleware Worker.
- Astro se contente de dĂ©tecter la prĂ©sence du cookie (ou dâun header injectĂ©) et dâadapter le client Sanity en consĂ©quence.
- Aucun contenu draft nâest jamais exposĂ© sans cookie/header valide.
- La documentation Starlight nâest pas concernĂ©e (elle reste 100 % Git-based).
4.7.2. Flux de fonctionnement
Section titled â4.7.2. Flux de fonctionnementâ[Sanity Studio] â (bouton "Open Preview") âŒURL de preview : https://votresite.com/api/preview?secret=XXX&slug=/blog/mon-article â âŒ[Cloudflare Middleware Worker] â 1. Valide le secret â 2. Pose un cookie httpOnly + Secure : `sanity-preview=true` â 3. (Optionnel) Injecte le header `x-sanity-preview: true` â 4. Redirige vers la page demandĂ©e âŒ[Astro â Pages Functions / SSR] â DĂ©tecte le cookie ou le header â Configure le client Sanity : â perspective: 'previewDrafts' â useCdn: false â token: SANITY_API_READ_TOKEN (lecture drafts) âŒAffichage du contenu draft dans le vrai design du site4.7.3. RĂšgles de sĂ©curitĂ©
Section titled â4.7.3. RĂšgles de sĂ©curitĂ©â- Le secret de preview est stockĂ© en variable dâenvironnement (Cloudflare + Sanity).
- Cookie :
httpOnly,Secure,SameSite=Lax, durĂ©e de vie limitĂ©e (ex. 1 heure). - Le token dâAPI utilisĂ© en mode preview possĂšde uniquement les droits de lecture des drafts.
- Une route de sortie (
/api/exit-preview) permet de supprimer le cookie et de revenir en mode publiĂ©. - Les webhooks de rebuild ne sont dĂ©clenchĂ©s que sur lâĂ©vĂ©nement
publish(jamais sur les sauvegardes de draft).
4.7.4. Responsabilités par couche
Section titled â4.7.4. ResponsabilitĂ©s par coucheâ| ResponsabilitĂ© | Couche / Composant |
|---|---|
| Validation du secret + pose du cookie | Cloudflare Middleware Worker (L3) |
| Détection du mode preview | Astro (lecture cookie/header) |
| RequĂȘtage des drafts (GROQ) | Astro (client Sanity conditionnel) |
| Rendu de la page | Astro (SSR via Pages Functions) |
4.8. Gestion des Médias (Cloudflare R2 + Stream)
Section titled â4.8. Gestion des MĂ©dias (Cloudflare R2 + Stream)âTous les fichiers mĂ©dias du projet (images, vidĂ©os, PDF, documents) sont stockĂ©s exclusivement sur Cloudflare R2. Sanity ne conserve que les mĂ©tadonnĂ©es et les URLs pointant vers R2. Cette approche garantit la cohĂ©rence avec lâĂ©cosystĂšme Cloudflare (Couche 03), un contrĂŽle total des coĂ»ts et une performance Edge native.
4.8.1. Architecture de stockage
Section titled â4.8.1. Architecture de stockageâ[Upload depuis Sanity Studio] â âŒ[Worker dâupload (Cloudflare)] â âą RĂ©ception du fichier â âą Stockage structurĂ© dans R2 â âą Retour de lâURL publique (ou signĂ©e) âŒ[Sanity] â Enregistre uniquement les mĂ©tadonnĂ©es + lâURL R2 âŒ[Astro / Site] â Consomme les URLs R2 (images) ou Cloudflare Stream (vidĂ©os)4.8.2. Organisation des chemins R2
Section titled â4.8.2. Organisation des chemins R2âmedias/âââ images/â âââ blog/â âââ portfolio/â âââ pages/âââ videos/ â fichiers sources (si besoin)âââ documents/â âââ pdf/â âââ autres/âââ temp/ â uploads temporaires (nettoyage automatique)4.8.3. Traitement par type de mĂ©dia
Section titled â4.8.3. Traitement par type de mĂ©diaâ| Type de mĂ©dia | Solution | DĂ©tails |
|---|---|---|
| Images | Cloudflare R2 + CDN | Optimisation possible via Cloudflare Images / Polish. Les métadonnées (alt, hotspot, dimensions) restent gérées dans Sanity. |
| VidĂ©os | Cloudflare Stream | Upload vers Stream (transcoding, adaptive bitrate, lecteur intĂ©grĂ©). LâUID Stream est stockĂ© dans Sanity. |
| PDF & Documents | Cloudflare R2 | Lien direct ou prévisualisation. Miniatures générables via Worker si nécessaire. |
| Autres fichiers | Cloudflare R2 | Stockage générique. |
4.8.4. Politique de nettoyage (Garbage Collection)
Section titled â4.8.4. Politique de nettoyage (Garbage Collection)â- Fichiers orphelins : tout fichier prĂ©sent dans R2 qui nâest plus rĂ©fĂ©rencĂ© par aucun document Sanity depuis 30 jours est considĂ©rĂ© comme orphelin.
- Job de nettoyage : Worker planifié (Cron Trigger) exécuté 1 fois par semaine. Il compare le contenu de R2 avec les URLs réellement utilisées dans Sanity (via GROQ) et supprime les orphelins.
- Dossier
temp/: nettoyage automatique tous les 7 jours. - Archivage optionnel : les fichiers supprimĂ©s peuvent ĂȘtre dĂ©placĂ©s vers un bucket dâarchive pendant 90 jours avant suppression dĂ©finitive.
- Audit : chaque suppression est journalisée (Logpush ou logs Worker).
4.8.5. Sécurité et accÚs
Section titled â4.8.5. SĂ©curitĂ© et accĂšsâ- Buckets R2 configurĂ©s en accĂšs contrĂŽlĂ©.
- URLs publiques pour les médias destinés au site.
- URLs signées (expirantes) pour les documents sensibles si nécessaire.
- Les credentials R2 et Stream sont injectés via
wrangler secret(jamais dans le code source).
4.9. Gouvernance, RĂŽles, Backup & Alerting
Section titled â4.9. Gouvernance, RĂŽles, Backup & Alertingâ4.9.1. RĂŽles et permissions dans Sanity Studio
Section titled â4.9.1. RĂŽles et permissions dans Sanity Studioâ| RĂŽle | Droits principaux | Public cible |
|---|---|---|
| Administrator | AccĂšs total (schĂ©mas, settings, tous les documents, gestion des utilisateurs) | Ăquipe Architecture / Tech Lead |
| Editor | CrĂ©ation, modification et publication de tous les contenus (posts, projects, pages, authors, categories) | Ăquipe Marketing / Contenu |
| Contributor | Création et modification de contenus, sans droit de publication (reste en draft) | Rédacteurs externes ou juniors |
| Viewer | Lecture seule | Stakeholders / relecture |
- LâaccĂšs au Studio est protĂ©gĂ© par authentification obligatoire (OAuth Google / GitHub).
- Seuls les Administrators peuvent modifier les schémas et configurer les webhooks.
4.9.2. Stratégie de Backup
Section titled â4.9.2. StratĂ©gie de Backupâ| ĂlĂ©ment | Solution | FrĂ©quence / RĂ©tention |
|---|---|---|
| Contenu Sanity (documents) | Export automatisĂ© via lâAPI Sanity vers un bucket R2 dĂ©diĂ© backups/sanity/ |
Quotidien â rĂ©tention 30 jours |
| Médias (R2) | Versioning activé sur les buckets R2 | Versioning natif |
| Schémas Sanity | Versionnés dans le dépÎt Git (sanity-studio) |
Ă chaque commit |
| Documentation Starlight | Déjà dans Git | Standard Git |
Un Worker planifiĂ© (ou GitHub Action) exĂ©cute quotidiennement lâexport du dataset production et purge les archives de plus de 30 jours.
4.9.3. Alerting Webhook & Opérations
Section titled â4.9.3. Alerting Webhook & OpĂ©rationsâ| ĂvĂ©nement | Action |
|---|---|
| Ăchec du webhook de rebuild (Sanity â hĂ©bergeur) | Alerte immĂ©diate |
Taux dâerreur Ă©levĂ© sur cms-webhook-receiver |
Alerte |
Ăchec du job media-cleanup |
Alerte |
| Ăchec du job de backup Sanity | Alerte |
Les alertes sont envoyées par email et/ou canal Slack/Teams.
Le Worker cms-webhook-receiver (voir DAT L3) est responsable de dĂ©tecter les Ă©checs de dĂ©clenchement de rebuild et de lever lâalerte.
5. Flux dâIntĂ©gration et API
Section titled â5. Flux dâIntĂ©gration et APIâLa communication entre la Couche 01 et la Couche 02 sâeffectue principalement au moment du Build de lâapplication (Static Site Generation - SSG).
- Protocole de RequĂȘtage (Sanity) : Utilisation du langage GROQ (Graph-Relational Object Queries) via le client officiel
@sanity/client. - Gestion des Fichiers (Starlight) : Utilisation des
getCollection('docs')natifs dâAstro pour lire et valider les fichiers Markdown locaux via les schĂ©mas dâAstro (Zod). - Mise Ă jour du contenu (Webhooks) :
- Une modification dans Sanity dĂ©clenche un webhook HTTP POST vers la plateforme dâhĂ©bergement (ex: Vercel, Netlify).
- Ce webhook force le redéploiement et le re-build automatique du site Astro pour intégrer le nouveau contenu.
- Recherche unifiée : Le contenu Sanity (une fois rendu en HTML) est indexé avec la documentation Starlight par Pagefind lors du build (voir DAT L2 §8).
6. Sécurité et Performances
Section titled â6. SĂ©curitĂ© et Performancesâ6.1. SĂ©curitĂ©
Section titled â6.1. SĂ©curitĂ©â- Jetons dâaccĂšs (Tokens) : Les requĂȘtes GROQ en lecture lors du build utilisent un token dâAPI en lecture seule, injectĂ© comme variable dâenvironnement (
SANITY_API_READ_TOKEN). LâaccĂšs public Ă lâAPI du Content Lake est dĂ©sactivĂ©. - Authentification Studio : LâaccĂšs Ă lâinterface dâĂ©dition Sanity Studio est restreint via une authentification obligatoire (OAuth2 / Google / GitHub) configurĂ©e sur le projet Sanity.
6.2. Performances
Section titled â6.2. Performancesâ- Mise en cache (Caching) : Le contenu Ă©tant massivement statique (gĂ©nĂ©rĂ© au build), lâimpact de la latence de lâAPI Sanity est nul pour lâutilisateur final.
- Optimisation CDN : Les médias sont servis exclusivement depuis Cloudflare R2 (images et documents) et Cloudflare Stream (vidéos), avec cache Edge agressif et optimisations natives (Polish / Cloudflare Images si activé).