/aeo-geo-bestpractices — SEO + AEO + GEO + Lighthouse 100 Playbook
Workflow complet pour transformer n'importe quelle page ou SPA en une machine de découverte organique (SEO), moteurs de réponse (AEO) et moteurs génératifs/agents IA (GEO), tout en garantissant des scores de 98 à 100 dans Google Lighthouse 13 (Desktop et Mobile) dans les 5 catégories : Performance, Accessibilité, Bonnes Pratiques, SEO et Agentic Browsing.
Chemins : <skill> est le dossier où se trouve ce SKILL.md.
0. Alignement Rapide Avant d'Exécuter
0.1. Détectez la stack et lisez le guide correspondant
Les règles de ce fichier s'appliquent à tout site web. Ce qui change entre les frameworks (où se trouve le <head>, le dossier public, comment pré-rendre, comment optimiser les images et éviter le TBT, quelle commande lance l'aperçu) se trouve dans references/frameworks/. Identifiez la stack à partir du projet et lisez le guide avant d'appliquer les étapes 1 à 5 :
| Indice dans le projet | Stack | Guide |
|---|---|---|
angular.json ou @angular/core | Angular | references/frameworks/angular.md |
next.config.* ou next | Next.js | references/frameworks/nextjs.md |
nuxt.config.* ou nuxt / vite.config.* + vue | Nuxt / Vue + Vite | references/frameworks/vue-nuxt.md |
astro.config.* ou astro | Astro | references/frameworks/astro.md |
vite.config.* + react | Vite + React | references/frameworks/vite-react.md |
index.html sans framework | HTML statique | references/frameworks/html-estatico.md |
Si la stack n'est pas dans la liste (ex. : SvelteKit, Remix, Gatsby, WordPress), appliquez les règles générales de ce fichier et utilisez le guide le plus similaire comme référence : les frameworks avec SSR/SSG suivent la logique de Next/Nuxt ; les SPA suivent celle de Vite + React.
0.2. Confirmez avec l'utilisateur
Avant de modifier le code, confirmez (ou extrayez du projet lui-même si déjà explicite) :
- Domaine canonique de production (ex. :
https://monsite.com/). S'il n'y a pas encore de domaine défini, demandez ou utilisez un placeholder clair et avertissez l'utilisateur. - FAQ Visible vs. Invisible (uniquement AEO/GEO) :
- Demandez si l'utilisateur souhaite une section FAQ visible dans la mise en page (
<details>/<summary>) ou uniquement invisible en coulisses (dans le@graphJSON-LD +llms.txt+<noscript>) pour ne pas modifier le design visuel.
- Demandez si l'utilisateur souhaite une section FAQ visible dans la mise en page (
- Préférence de suivi pour l'entraînement (lors de la création ou modification de
robots.txt) :- Vérifiez si le site a déjà défini une préférence. S'il n'y a pas de choix clair, proposez deux options : autoriser la recherche et bloquer le suivi pour l'entraînement d'OpenAI et d'Anthropic ; ou autoriser également ce suivi pour l'entraînement. Préservez les autres règles du site.
1. SEO Technique Complet (index.html, robots.txt, sitemap.xml)
Consultez les modèles prêts à l'emploi dans references/templates.md.
- Balises Meta Essentielles dans le
<head>:<html lang="fr-FR">(ou la langue du projet).<title>fort (50-65 caractères) avec Nom de la Marque/Expert + Proposition Principale.<meta name="description">(140-165 caractères) répondant clairement à ce que c'est, pour qui et ce que cela inclut.<meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1" /><link rel="canonical" href="https://domaine.com/" />+<link rel="alternate" hreflang="fr-FR" ... />etx-default.- Open Graph complet (
og:type,og:locale,og:site_name,og:title,og:description,og:url,og:image,og:image:width,og:image:height,og:image:alt) et Twitter Cards (summary_large_image).
public/robots.txtAmical pour les Moteurs de Recherche et les IA :- Pour rester visible dans les recherches ChatGPT et Claude, autorisez
OAI-SearchBotetClaude-SearchBot.Claude-Userpeut rechercher des pages en réponse aux demandes des utilisateurs ;ChatGPT-Userest également déclenché par les utilisateurs, mais ne contrôle pas l'inclusion dans la recherche ChatGPT. GPTBotetClaudeBotsont les bots documentés pour une possible utilisation du contenu dans l'entraînement. Si l'utilisateur choisit de bloquer ce suivi, utilisez des groupes dédiés avecDisallow: /pour les deux, sans bloquer les bots de recherche. S'il choisit de l'autoriser, utilisezAllow: /pour les deux. Voir les deux variantes dansreferences/templates.md.- Traitez les contrôles d'autres fournisseurs, comme
Google-Extended, séparément. Autoriser l'accès aux bots de recherche augmente l'éligibilité, mais ne garantit pas la citation ou la présence dans les réponses. Consultez les directives d'OpenAI et d'Anthropic lors de la mise à jour de la liste des bots. - Pointez vers
Sitemap: https://domaine.com/sitemap.xml.
- Pour rester visible dans les recherches ChatGPT et Claude, autorisez
public/sitemap.xmlRiche (Images + Vidéo) :- Incluez les namespaces
xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"etxmlns:video="http://www.google.com/schemas/sitemap-video/1.1"lorsqu'il y a des images à la une et des vidéos de vente/présentation.
- Incluez les namespaces
2. AEO — Answer Engine Optimization (Featured Snippets, Voix et Crawlers sans JS)
Les moteurs de réponse ont besoin d'entités connectées et d'HTML sémantique lisible sans exécuter JavaScript :
- Graphe Connecté
@graphenSchema.orgJSON-LD :- Au lieu de scripts JSON-LD dispersés, créez un unique
<script type="application/ld+json">avec@graphconnectant les entités par@id(voir le modèle dansreferences/templates.md) :WebSite(#website)PersonouOrganization(#person/#org) avecsameAspour les réseaux sociaux officielsWebPage(#webpage) avecaboutetmainEntityProduct/SoftwareApplication/Service(#product) avecoffersCourse(#course) lorsqu'il y a un cours/formation/bonus éducatifVideoObject(#video) lorsqu'il y a une VSL ou une vidéo de présentationFAQPage(#faq) avec 4 à 6 vraies questions à forte intention de recherche et des réponses directes et complètes (40-70 mots chacune).
- Au lieu de scripts JSON-LD dispersés, créez un unique
- Fallback Sémantique
<noscript>pour les SPA (React / Vue / Vite) :- S'applique uniquement aux SPA sans pré-rendu. Dans Next.js, Nuxt, Astro, Angular avec SSR/prerender et HTML statique, le HTML arrive déjà complet et le
<noscript>est inutile (voir le guide de la stack). - Les crawlers de LLM (
GPTBot,ClaudeBot,PerplexityBot,OAI-SearchBot) ne récupèrent souvent que le HTML brut et n'exécutent pas le bundle JavaScript. - Dans les projets SPA où
<div id="root"></div>est vide, ajoutez un bloc<noscript><main>...</main></noscript>dans leindex.htmlcontenant le<h1>, le résumé de l'offre, la liste des livrables (<ul><li>) et un lien direct vers le CTA/checkout. - Important pour la Performance : Utilisez
<noscript>(et non des éléments dans<div id="root">lorsque vous utilisezcreateRoot().render()), car placer du DOM dans#rootsans SSR force React à démonter et recréer tout l'arbre au montage, retardant le LCP et générant du TBT.
- S'applique uniquement aux SPA sans pré-rendu. Dans Next.js, Nuxt, Astro, Angular avec SSR/prerender et HTML statique, le HTML arrive déjà complet et le
3. GEO & Agentic Browsing (Score 100 dans Lighthouse 13)
Google Lighthouse 13 a introduit la catégorie Agentic Browsing, qui audite si la page est préparée pour les agents autonomes et les LLM (llms-txt et ard-schema).
public/llms.txt+ Lien dans le<head>:- Créez
public/llms.txten Markdown structuré (voirreferences/templates.md) contenant :# Nom du Produit / Marque> Résumé en une phrase de la proposition de valeur- Sections claires :
## À Propos,## Ce Qui Est Inclus,## Bonus / Conditions,## Questions Fréquentes (AEO / GEO),## Liens Officiels.
- Dans le
<head>duindex.html, ajoutez :<link rel="help" type="text/plain" href="/llms.txt" title="LLMs.txt — Informations structurées pour les IA" />
- Créez
public/.well-known/ard.json(Agent Resource Discovery v0.91) :- Dans les SPA (Vite, Next, etc.), les routes inexistantes comme
/.well-known/ard.jsonrenvoient leindex.html(<!doctype html>) par fallback, ce qui fait échouer l'auditard-schemade Lighthouse 13 avec une erreur de parsing JSON. - Créez toujours le fichier
public/.well-known/ard.jsonen suivant le schéma officiel ARD v0.91 (modèle exact dansreferences/templates.md) :- Doit contenir
ardVersion: "0.91.0"etentriesavecidentifierau formaturn:air:<domaine>:<type>:<slug>,displayName,type: "application/agent-skills+md",urlpointant vers/llms.txt,descriptionetrepresentativeQueries.
- Doit contenir
- Dans les SPA (Vite, Next, etc.), les routes inexistantes comme
4. Google Lighthouse 100/100 : Performance & Accessibilité
Pour atteindre 98-100 sur Mobile (4G Simulé + 4x CPU Throttling) et 100 sur Desktop, appliquez rigoureusement ces règles :
4.1. LCP (Largest Contentful Paint < 2.1s sur Mobile)
- Preload en Haut du
<head>:- Placez le
<link rel="preload" as="image" ... fetchpriority="high">juste en dessous du<title>(dans les 15 premières lignes duindex.html), avant les dizaines de balises meta Open Graph et le JSON-LD.
- Placez le
- Calibrage de
srcSetetsizespour le DPR1.75xdu Lighthouse Mobile :- Le Lighthouse Mobile émule un Moto G Power (
412pxde largeur avecdevicePixelRatio: 1.75). - Si une image a
sizes="(max-width: 767px) 260px, 500px", Chrome calcule260 * 1.75 = 455w. Si la version mobile dans lesrcSetest déclarée comme260w(inférieure à455w), Chrome ignore l'image mobile et télécharge la version desktop lourde ! - Règle d'or : Déclarez l'image mobile dans le
srcSetavec une largeur>= ceil(largeur_css_mobile * 1.8)(ex. :450wpour des images de240px, ou680wpour des images de360pxpleine largeur sur mobile) et compressez en WebP (< 30 KB).
- Le Lighthouse Mobile émule un Moto G Power (
- Évitez que les Images Décoratives de Fond "Volent" le LCP :
- Si la section Hero a une photo décorative de fond (
opacity: 0.15) avec une boîte (boundingRect) plus grande que le portrait principal ou le<h1>, Chrome la considérera comme l'élément LCP. - Sur mobile (
@media (max-width: 767px)), masquez les photos de fond géantes purement décoratives (display: none) ou réduisez leurs dimensions pour que l'élément principal (préchargé avecfetchpriority="high") soit le seul candidat au LCP.
- Si la section Hero a une photo décorative de fond (
- Ne Faites Pas de Preload de Polices qui Entrent en Concurrence avec le JS/CSS Critique dans les SPA :
- Utilisez
font-display: swapdans le@font-faceet laissez le navigateur prioriser le téléchargement du CSS et du bundle JS initial avant la police secondaire.
- Utilisez
4.2. TBT Zéro (0ms Total Blocking Time)
- Remplacez les Animations Continues en JS par CSS Compositior +
IntersectionObserver:- Les bibliothèques d'animation complètes dans le bundle initial ajoutent
50-150 KBde JS et bloquent le thread principal lors de l'hydratation. - Pour les révélations au scroll, utilisez
IntersectionObservernatif alternant une classe.is-visibleavectransition: opacity, transformaccélérée par GPU (translate3d). - Pour les particules ou rotations continues, utilisez
@keyframesen CSS avectransform: translate3d(...)/rotate(...)etwill-change: transform.
- Les bibliothèques d'animation complètes dans le bundle initial ajoutent
- Attention aux Filtres SVG (
<feTurbulence>,drop-shadow) et SMIL<animate>au Chargement :- N'utilisez jamais
<feTurbulence>en plein écran sans dimensions fixes (il recalcule le bruit de Perlin sur des millions de pixels sur le CPU, générant des Long Tasks de300ms+). Préférez unradial-gradientléger en CSS. - Ne combinez jamais
filter: drop-shadow(...)sur un élément parent dont le SVG enfant subit une mutation de géométrie à chaque frame lors du chargement initial. S'il y a un morphing de<path>via<animate attributeName="d">, activez le morphing uniquement après la première interaction utilisateur (pointermove,touchstart,wheel) ou après le chargement initial (setTimeoutde8000ms), en maintenant uniquement la rotation CSS sur le GPU pendant le chargement.
- N'utilisez jamais
- Code-Splitting du Smooth Scroll (
Lenis) :- Chargez
Lenisdynamiquement (await import("lenis")) uniquement au premier événementwheel,touchstartoukeydown({ passive: true, once: true }). Ainsi, il pèse0 KBet0msde CPU lors du chargement initial.
- Chargez
- Isolez les Timers (
setInterval/ Compteurs d'Urgence) dans des Composants Feuille :- Ne placez jamais un hook
useCountdown(setIntervalde 1s) dans le composant racine<App />. Cela force React à re-rendre toute la page chaque seconde pendant le test Lighthouse. Isolez l'état de l'horloge dans un petit composant<CountdownClock />qui rend uniquement les chiffres.
- Ne placez jamais un hook
4.3. Accessibilité Score 100 (WCAG AA)
- Contraste des Couleurs (
color-contrast) :- Les petits textes sur fond foncé (
#10100f/#171512) nécessitent un ratio de contraste>= 4.5:1. Utilisez des tons clairs pour les textes secondaires (#c4beb4,#d6c5b2) et des oranges lumineux pour les surbrillances sur fond foncé (#ff9452,#ffa36b). - Les boutons avec fond orange vif (
#ff772f) doivent utiliser un texte foncé (#140c06) pour un contraste maximal.
- Les petits textes sur fond foncé (
- Cohérence entre Texte Visible et Nom Accessible (
label-content-name-mismatch) :- Ne mettez jamais un
aria-labelsur un lien/bouton qui ne contient pas exactement le texte visible à l'intérieur (ex. : si le logo ditCF ZANELLA., ne mettez pasaria-label="Accueil").
- Ne mettez jamais un
- Sémantique et Clavier :
- Incluez
<a class="skip-link" href="#contenu">Passer au contenu</a>comme premier focus du<body>. - Utilisez une hiérarchie stricte des titres (
h1->h2->h3),aria-labelledbydans toutes les<section>, etaria-presseddans les sélecteurs d'état.
- Incluez
5. Validation Automatisée avec Google Lighthouse
Validez toujours les modifications en exécutant le script automatisé de la compétence :
# Projet local : détecte la stack, exécute `npm run build` (si existant) et lance l'aperçu sur le port 4173
node <skill>/scripts/run-lighthouse.mjs
# Ignorer la build (lorsque la sortie de build est déjà à jour)
...
[Le contenu a été tronqué pour la traduction]