PD-229 — Plan d'implémentation
📚 Navigation User Story
| Document | | | ---------- | -- | | 📋 [Spécification](PD-229-specification.md) | | | 🛠️ **Plan d'implémentation** | *(ce document)* | | ✅ [Critères d'acceptation](PD-229-acceptability.md) | | | 📝 [Retour d'expérience](PD-229-rex.md) | | [← Retour à site-vitrine](../PD-225-epic.md) · [↑ Index User Story](index.md)
1. Découpage en composants
Structure SEO
src/
├── components/
│ └── SEO.astro # Composant meta tags unifié
├── seo/
│ ├── schemas/
│ │ ├── organization.ts # JSON-LD Organization
│ │ ├── website.ts # JSON-LD WebSite
│ │ └── faq.ts # JSON-LD FAQPage
│ └── config.ts # Configuration SEO globale
├── layouts/
│ └── BaseLayout.astro # Intègre SEO.astro
└── pages/
└── *.astro # Props SEO par page
public/
├── og/ # Images OpenGraph (1200x630)
├── robots.txt # Directives crawlers
└── sitemap.xml # Plan du site (ou généré)
Composants clés
| Composant | Responsabilité |
SEO.astro | Génération meta tags + JSON-LD |
seo/schemas/*.ts | Schémas JSON-LD typés |
robots.txt | Directives crawlers |
sitemap.xml | URLs indexables avec hreflang |
2. Flux techniques
Injection SEO
Page.astro
│
└── <BaseLayout title={} description={} ogImage={}>
│
└── <SEO {props} />
│
├── <title>
├── <meta name="description">
├── <link rel="canonical">
├── <meta property="og:*">
├── <meta name="twitter:*">
└── <script type="application/ld+json">
Génération sitemap
astro build
│
└── @astrojs/sitemap
│
├── Collecte toutes les pages
├── Ajoute hreflang alternates
└── Génère dist/sitemap-index.xml
3. Mapping invariants → mécanismes
| Invariant | Mécanisme technique |
| INV-1 : title + description | Props obligatoires SEO.astro |
| INV-2 : JSON-LD valide | Schémas TypeScript validés |
| INV-3 : Métadonnées SEO explicites | Composant SEO.astro |
| INV-4 : JSON-LD conforme schema.org | Validation build-time |
Composant SEO.astro
---
// src/components/SEO.astro
interface Props {
title: string;
description: string;
canonical?: string;
ogImage?: string;
ogType?: 'website' | 'article';
noindex?: boolean;
jsonLd?: object | object[];
}
const {
title,
description,
canonical = Astro.url.href,
ogImage = '/og/default.png',
ogType = 'website',
noindex = false,
jsonLd
} = Astro.props;
const site = 'https://probatiovault.com';
const fullOgImage = ogImage.startsWith('http') ? ogImage : `${site}${ogImage}`;
---
<!-- SEO obligatoire -->
<title>{title}</title>
<meta name="description" content={description} />
<link rel="canonical" href={canonical} />
<meta name="robots" content={noindex ? 'noindex, nofollow' : 'index, follow'} />
<!-- OpenGraph obligatoire -->
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:type" content={ogType} />
<meta property="og:url" content={canonical} />
<meta property="og:image" content={fullOgImage} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:locale" content={Astro.currentLocale === 'en' ? 'en_US' : 'fr_FR'} />
<!-- Twitter Cards -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={fullOgImage} />
<!-- JSON-LD -->
{jsonLd && (
<script type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
)}
Schéma Organization
// src/seo/schemas/organization.ts
export const organizationSchema = {
"@context": "https://schema.org",
"@type": "Organization",
"name": "ProbatioVault",
"legalName": "ProbatioVault SASU",
"url": "https://probatiovault.com",
"logo": "https://probatiovault.com/logo.png",
"founder": {
"@type": "Person",
"name": "Loïc Pontani"
},
"address": {
"@type": "PostalAddress",
"streetAddress": "30 rue de la Varenne",
"addressLocality": "Saint-Maur-des-Fossés",
"postalCode": "94100",
"addressCountry": "FR"
}
};
Schéma WebSite
// src/seo/schemas/website.ts
export const websiteSchema = {
"@context": "https://schema.org",
"@type": "WebSite",
"name": "ProbatioVault",
"url": "https://probatiovault.com",
"potentialAction": {
"@type": "SearchAction",
"target": "https://probatiovault.com/fr/search?q={search_term_string}",
"query-input": "required name=search_term_string"
}
};
Configuration sitemap
// astro.config.mjs
import sitemap from '@astrojs/sitemap';
export default defineConfig({
site: 'https://probatiovault.com',
integrations: [
sitemap({
i18n: {
defaultLocale: 'fr',
locales: {
fr: 'fr-FR',
en: 'en-US'
}
},
filter: (page) => !page.includes('/admin/')
})
]
});
4. Gestion des erreurs
| Erreur | Cause | Mitigation |
| title manquant | Props non fournis | TypeScript required |
| JSON-LD invalide | Schéma mal formé | Validation schema.org |
| og:image 404 | Image non générée | CI vérifie existence |
| canonical incorrect | URL hardcodée | Utiliser Astro.url |
Script de validation SEO
#!/bin/bash
# scripts/validate-seo.sh
# Vérifier que chaque page HTML a les meta obligatoires
for html in dist/**/*.html; do
if ! grep -q '<title>' "$html"; then
echo "ERROR: title manquant dans $html"
exit 1
fi
if ! grep -q 'meta name="description"' "$html"; then
echo "ERROR: description manquante dans $html"
exit 1
fi
if ! grep -q 'og:image' "$html"; then
echo "ERROR: og:image manquant dans $html"
exit 1
fi
done
# Valider JSON-LD
npx jsonld-lint dist/**/*.html
5. Impacts sécurité
| Aspect | Mesure |
| XSS dans JSON-LD | Échappement JSON.stringify |
| Open redirect | Canonical = même domaine |
| Information disclosure | Pas de données sensibles dans meta |
6. Hypothèses techniques
| Hypothèse | Justification |
| @astrojs/sitemap stable | Plugin officiel Astro |
| Images OG 1200x630px | Standard OpenGraph |
| schema.org validateur externe | Google Rich Results Test |
| Pas de SSR pour sitemap | Génération build-time |
7. Points de vigilance
| Point | Risque | Action |
| Images OG | 14 images à générer (7 pages × 2 langues) | Template Figma |
| Canonical dupliqué | /fr/ vs /fr | Normaliser trailing slash |
| noindex en prod | Oubli flag | CI vérifie absence noindex |
| Sitemap à jour | Pages oubliées | Génération automatique |
| JSON-LD validité | Propriétés manquantes | Test Rich Results |
Fichiers à créer
| Fichier | Description |
src/components/SEO.astro | Composant meta unifié |
src/seo/schemas/organization.ts | Schéma Organization |
src/seo/schemas/website.ts | Schéma WebSite |
src/seo/schemas/faq.ts | Schéma FAQPage |
public/robots.txt | Directives crawlers |
public/og/*.png | Images OpenGraph |
scripts/validate-seo.sh | Validation CI |