# TODO — Fixes audits abc-guepes.com

> Source : `~/Documents/Abc/` (4 audits, 30 issues) · Plan établi le 2026-07-10
> Workflow : branche `fix/lot-N` depuis `dev`, merge ff dans `dev`, validation utilisateur avant prod.

## Phase 1 — Quick wins

- [x] **Lot 1 — Config : 301 apex→www + robots.txt** *(fait 2026-07-10, sauf action dashboard ci-dessous)*
  - [x] Redirect host-based permanente (308) apex→www ajoutée dans `vercel.json`. Diagnostic : l'apex redirigeait déjà (config Domains du dashboard Vercel) mais en **307 temporaire** + double saut en HTTP.
  - [ ] ⚠️ **ACTION MANUELLE** : dashboard Vercel → Settings → Domains → `abc-guepes.com` : passer la redirection en **Permanent (308)**, ou la retirer pour laisser la règle `vercel.json` prendre le relais. Optionnel : Redirect Rule Cloudflare 301 pour collapser le double saut HTTP.
  - [x] robots.txt : bloc 10 crawlers IA + `Content-Signal: search=yes, ai-input=yes, ai-train=yes`
  - [x] robots.txt : nettoyage complet (`Host:`, `/page-data/`, `/static/`, `Crawl-delay` bingbot, `/component---*`, groupes sociaux redondants)
  - [x] Comptage sitemap tranché : **665 URLs, 0 doublon** (le 862 de l'audit était erroné)
- [x] **Lot 2 — Quick wins code** *(fait 2026-07-10)*
  - [x] `lang="fr"` global via `gatsby-ssr.js` `setHtmlAttributes` (cause : les SmartLocalSeo* ne posaient jamais `htmlAttributes`, contrairement au seo.js homepage)
  - [x] `aria-label` sur les 2 liens icône-seule de la Navbar + SVG en `aria-hidden`/`focusable="false"`
  - [x] `text-wrap: balance` (h1-h3) + `text-wrap: pretty` (p) dans GlobalStyles.js
  - [x] `static/favicon.ico` multi-tailles 48/32/16 (généré via sharp + png-to-ico depuis logo.png) + `static/favicon.svg` (logo.svg recadré sur l'emblème guêpe, viewBox only) + links dans gatsby-ssr.js
  - Vérif post-build : `<html lang="fr">` sur pages villes, `/favicon.ico` et `/favicon.svg` en 200

## Phase 2 — Défauts critiques (conversion + trafic)

- [x] **Lot 3 — Navigation accessible** *(fait 2026-07-10, commit a7e1454)* : burger → `<button aria-expanded aria-controls>` + sr-only, méga-menu « Présentation » en disclosure (bouton + Échap + focus-within sur le panneau), plus aucun onClick sur div dans la nav, icône WhatsApp `#128C7E` (≥ 3:1). À valider au clavier en local (Tab → burger → Entrée → Échap).
- [x] **Lot 4 — Formulaire de devis** *(fait 2026-07-10, commit eb0f6cd)* : labels `for`/`id` sur les 5 champs, CAPTCHA math supprimé (honeypots conservés, hors écran + aria-hidden), erreurs `aria-invalid`/`aria-describedby`/`role="alert"` + CSS `:user-invalid`, `autocomplete`/`enterkeyhint`, fallback natif `action=POST` vers GetForm (sans JS le formulaire marche). Découverte : la soumission passe par **GetForm** (getform.io), pas EmailJS — CLAUDE.md à corriger. À valider visuellement avant prod : affichage des erreurs sur soumission à vide.
- [x] **Lot 5 — Maillage interne + breadcrumbs** *(fait 2026-07-10)* : `communesIndex.js` (622 communes, générées depuis les fichiers pages), `<CommunesIndex>` exhaustif groupé par lettre sur les 3 pages départements (chaque ville à 2 clics de l'accueil), lien /zones-tarifaires depuis les depts, `<Breadcrumb>` visible + JSON-LD depuis la même source (`utils/breadcrumbs.js`) — fini le `/91`→404 sur 622 pages ; villes 89 en trail 2 niveaux. Vérif post-build : rich results test sur une page ville, crawl profondeur ≤ 3.

## Phase 3 — Chantiers de fond mutualisés

- [x] **Lot 6 — Alt text** *(fait 2026-07-10, commit 519b51e)* : le constat d'audit était en grande partie un faux positif (couche placeholder de gatsby-plugin-image, correcte). Vrai défaut corrigé : 36 miniatures `alt="Thumbnail"` → « Miniature : <sujet> » sur les 3 galeries + 12 alt malformés/dupliqués réécrits sur galerie-guepes-2 après examen visuel des photos.
- [x] **Lot 7 — Composant Head/SEO commun** *(fait 2026-07-10, commit 294d83a)* : titles ≤ 62 partout sans téléphone/emoji (échelle de dégradation pour noms multi-villes longs), `clampDescription` coupe à la phrase/au mot (fini le substring(157)), color-scheme global, doublons viewport/theme-color supprimés, meta robots sur les 5 pages depts, vraie carte OG 1200×630 (`static/og-image.png`, vérifiée visuellement) déclarée sur toutes les surfaces. Note : `seo.js` et `SmartLocalSeo.js` sont du code mort (0 import) — candidats knip. Découverte : pages depts 75 et 93 existent déjà (utile Lot 15).
- [x] **Lot 8 — Prix** *(fait 2026-07-10, commit 34f5235)* : `src/data/pricing.js` source unique (grille zonée Châtelet 80-120 / Z1 90-130 / Z2 110-150 / Z3 130-170, global 80-170), JSON-LD `Offer.priceSpecification` par zone sur les 622 pages villes + accueil + /tarifs, llms.txt/llms-full.txt (v1.1.0) réconciliés, prose FAQ zone-aware. **Bug préexistant corrigé** : le lookup de zone ne retirait pas `.js` → toutes les villes tombaient en zone 1. Reste SEO 09 : streetAddress (adresse rue à demander), dimensions logo.
- [x] **Lot 9 — Bannière cookies** *(fait 2026-07-10, commit 48bb145)* : `popover="manual"` (bannière + panneau, top-layer = inert résolu nativement), `popoverTarget` sur Personnaliser/Annuler, H4 → `<p aria-labelledby>`, contrastes recalculés (gradient `#006e51→#00805f`, teal `#2dd4aa`, focus blanc 3px, forced-colors border), fallback gracieux si API absente, logique consentement/GA intacte. À valider visuellement en local (1re visite, accepter/refuser, personnaliser).

## Phase 4 — Finitions a11y + agent readiness

- [x] **Lot 10 — Contrastes dégradés** *(fait 2026-07-10, commit 21c782e)* : 3 cartes expertise de l'accueil assombries (3.82/2.54/2.19:1 → 6.06/4.94/4.95:1, ratios recalculés par script), forced-colors sur cartes + CTA téléphone.
- [x] **Lot 11 — Landmarks + skip link** *(fait 2026-07-10, commit 2413307)* : partition React.Children dans layout.js (0 page touchée) → header/main/footer corrects, skip link `#main-content`, roles redondants supprimés, headings galerie + liens-divers. Reco lot futur : rendre Heading/Footer par le layout lui-même (retirer des 646 pages).
- [x] **Lot 12 — /zones-tarifaires** *(fait 2026-07-10, commit 34e9ca0)* : vraies tables (caption, thead th scope=col, th scope=row commune, colgroup, scroll-x) sur les 3 pages — 511+166+47 communes, zéro perte.
- [x] **Lot 13 — Flux RSS** *(fait 2026-07-10, commit 24518c4)* : /rss.xml full-content (guid permalink, atom:link self, sy:updatePeriod), rel=alternate injecté par le plugin sur toutes les pages, référencé dans llms.txt + llms-full.txt.
- [x] **Lot 14 — Agent readiness avancé** *(fait 2026-07-10, commit 47bbce1)* : 659 endpoints .md (onPostBuild, data-driven), Link header global (llms.txt describedby + sitemap) + Content-Type text/markdown sur *.md. Reste : `<link rel=alternate type=text/markdown>` par page → à faire dans la migration Head API ; grille prix dupliquée en CJS dans markdownEndpoints.js (synchro manuelle avec pricing.js).

## Phase 5 — Backlog / décisions

- [ ] **Lot 15 — Backlog**
  - ⚠️ DÉCISION éditoriale : couvrir ou retirer les mentions 75/93 — `audit-seo/issues/12-coherence-geographique.md`
  - Container queries au fil de l'eau — `audit-foundations/issues/04-container-queries.md`

## Chantiers post-audits identifiés

- [~] **Performance (Lighthouse mobile local : perf 74 au départ)** — fait 2026-07-10 :
  - [x] Hydratation React réparée (#418 ×6 + #423, préexistants même en prod) : cause = `<div>` dans `<p>` (Footer SuperBold) + props non-transient + attributs popover camelCase + prop `priority` invalide. **0 erreur console sur le build de prod.** (commit 93893e5)
  - [x] GA4 + Google Ads : chargement inconditionnel retiré (RGPD) — uniquement après consentement, toggle marketing enfin câblé (97e5913)
  - [x] @vercel/speed-insights retiré (f17f7a5)
  - [x] Bundle analyzer activable par `ANALYZE=true pnpm build` → public/bundle-analyzer-report.html
  - [ ] ⚠️ DÉCISION GTM (`GTM-TM9JHNX`, toujours inconditionnel) : (1) retirer si le conteneur ne fait que GA4/Ads, (2) gater derrière consentement, (3) Consent Mode v2 côté GTM
  - [ ] Lazy-loading candidats (deps déjà installées : @loadable/component) : LocalMap (iframe OSM sur 622 pages, plus gros levier), section FAQ SmartLocalContent, ZoomClient à l'interaction
  - [ ] gatsby-plugin-preact à tester (~30-35 KiB gzip de runtime en moins, compat stack jugée bonne)
  - [ ] Re-passer Lighthouse mobile après déploiement pour mesurer le gain

## Décisions actées (2026-07-10)

1. **Prix réel** (Lot 8) : ✅ grille **zonée** (précisée par l'utilisateur) : Le Châtelet-en-Brie 80-120 € · Zone 1 90-130 € · Zone 2 +20 € (110-150 €) · Zone 3 +40 € (130-170 €). Range global 80-170 €. Source de vérité unique `src/data/pricing.js`, alignée sur les pages /zones-tarifaires.
2. **Couverture géo** (Lot 15) : ✅ **ABC Guêpes couvre réellement 75 et 93** — on garde les mentions. Nouveau chantier contenu à prévoir : pages départementales 75 + 93, zones tarifaires associées, communes 93 (à cadrer avec l'utilisateur au moment du Lot 15 — volumétrie : 40 communes 93 + 20 arrondissements 75 potentiels).

## Notes techniques

- ⚠️ **Vercel ne déploie AUCUNE preview des pushes** *(constaté 2026-07-10)* : statut GitHub « Git author bastienallain must have access to the project on Vercel to create deployments ». → Ajouter le compte GitHub `bastienallain` comme membre du projet Vercel (Settings → Members), ou committer avec l'email du compte Vercel propriétaire. Tant que ce n'est pas réglé, la migration pnpm et tous les fixes ne sont PAS validés sur l'infra Vercel, et le déploiement prod depuis ces commits sera bloqué pareil.

- ⚠️ **React 19 incompatible avec react-helmet (SSR)** *(diagnostiqué 2026-07-10)* : la migration pnpm locale était passée à React 19.2.7 → ~45 % des pages (270 villes + 9 statiques, ensemble aléatoire) perdaient TOUT leur head Helmet (title, metas, JSON-LD) au build. **React re-pinné à ^18.3.1** dans package.json (reste de la migration pnpm conservé). Ne pas remonter à React 19 avant migration des composants SEO vers la **Gatsby Head API** (react-helmet est non maintenu depuis 2020). Preuve : build React 18 = 0/646 pages sans title.
- **Polish ultérieur** : titles des communes à article (« à Le Chatelet En Brie » → « au Châtelet-en-Brie ») — les noms sont reconstruits depuis les slugs, sans accents ni contraction.

- **ESLint cassé** : ESLint v10 installé mais seul un `.eslintrc.js` legacy existe (pas de `eslint.config.js` flat) → `npm run lint` échoue repo-wide. Candidat idéal pour migrer vers Oxlint (toolchain oxc). À traiter hors lots d'audit.
- **Warning webpack `mini-css-extract-plugin` "Conflicting order"** (pnpm dev) : bénin — `normalize.css` et `react-medium-image-zoom/dist/styles.css` sont importés dans un ordre différent entre `index.js` et `galerie-guepes-2.js`. Aucun impact visuel (les deux CSS ne se recouvrent pas). Fix propre : importer les CSS globaux dans un ordre unique (ex. tout dans `gatsby-browser.js`/layout) ; sinon silençable via `ignoreOrder`. À traiter au fil de l'eau, pas bloquant.
