# Journal de développement — abc-guepes.com

Historique cumulatif des modifications. À lire au début d'une nouvelle session pour avoir le contexte.

---

## Session du 15 mai 2026

### Objectif
Maximiser la différenciation du contenu entre les 600+ pages de villes pour améliorer l'indexation Google (474 pages "crawlées mais non indexées" — contenu mince/dupliqué).

### Fichiers modifiés

#### `src/utils/contentVariations.js`
- Pools de variants étendus de 5 → 8 items : `CARD_GUEPES`, `CARD_FRELONS_EURO`, `CARD_FRELONS_ASIA`, `CARD_ABEILLES`, `CARD_BOURDONS`, `CONSEIL_LOCAL_CONCLUSIONS`, `CHENILLES_PERIODE_METHODE`, `DERAT_METHODE`
- Nouveaux exports : `DANGER_PHRASES`, `COMPLICATION_PHRASES`, `DEVELOPPEMENT_PHRASES`, `CONFIER_PRO_PHRASES` (5 variantes chacun)
- Nouveaux exports : `LIENS_GUEPES_ACCROCHES`, `LIENS_CHENILLES_ACCROCHES`, `LIENS_DERAT_ACCROCHES` (5 objets `{titre, sous}` chacun)
- Nouvelle fonction : `generateSaviezVous(cityName, postalCode)` — fait local par département (77/91/94)

#### `src/components/SmartLocalContent.js`
- Import des 7 nouvelles constantes/fonctions de contentVariations.js
- Variables hash : `dangerPhrase`, `complicationPhrase`, `developpementPhrase`, `confierProPhrase`, `lienGuepes`, `lienChenilles`, `lienDerat`, `saviezVous`
- Section "Pourquoi faire appel" : 3 paragraphes variabilisés
- Section "Notre expertise" : `confierProPhrase` remplace le texte fixe
- Nouvelle section "Le saviez-vous ?" conditionnelle (box verte, après bloc Engagement Écologique)
- Section liens (3 cartes) : titres/sous-titres variabilisés
- Protections anti-page-blanche : try/catch sur `generateSaviezVous`, optional chaining, fallbacks littéraux sur toutes nouvelles variables

#### `src/components/SmartLocalSeoOptimized.js`
- `generateDescription()` enrichie avec `locationData` (quartiers + temps_intervention) pour varier les meta descriptions

#### `src/data/locationData.js`
- ~39 nouvelles villes enrichies ajoutées (77, 91, 94), total ~91 entrées
- Nouvelles 77 : provins, nemours, mitry-mory, ozoir-la-ferriere, ferriere-en-brie, chessy, vaires-sur-marne, claye-souilly, rozay-en-brie, nangis, moret-sur-loing, saint-thibault-des-vignes
- Nouvelles 91 : longjumeau, juvisy-sur-orge, arpajon, etampes, chilly-mazarin, bretigny-sur-orge, epinay-sur-orge, wissous, bondoufle
- Nouvelles 94 : charenton-le-pont, maisons-alfort, arcueil, cachan, fresnes, rungis, villeneuve-le-roi, limeil-brevannes, bonneuil-sur-marne, villecresnes, ablon-sur-seine

### Points d'attention conservés
- Clé `ferriere-en-brie-77164` sans accent final (cohérent avec le nom de fichier page)
- `generateSaviezVous` — départements 89 utilisent les faits du 77 par défaut
- Meta descriptions : troncature à 157 caractères + "..."

---

## Session du 20 mai 2026

### Objectifs
1. Corriger erreurs FAQ (`soiMeme` contradictoire, délais irréalistes)
2. Améliorer différenciation contenu sur 4 axes supplémentaires (chenilles H2, dératisation H2, maillage, locationData)
3. Résoudre "no screenshot available" sur Vercel
4. Merger `fix/indexation-issues` → `master`
5. Ajouter 250 villes enrichies supplémentaires

### Fichiers modifiés

#### `gatsby-config.js`
- **Suppression de `gatsby-plugin-gatsby-cloud`** (ligne ~143)
  - Ce plugin générait `public/_headers.json` avec `X-Frame-Options: DENY`
  - Bloquait le service de screenshot de Vercel → "no screenshot available"
  - Remplacé par les headers dans `vercel.json` (déjà en place : `X-Frame-Options: SAMEORIGIN`)

#### `src/utils/contentVariations.js`

**Corrections FAQ :**
- `soiMemeAnswers` : les 3 variants commençaient "Non, c'est dangereux" — corrigé en "Oui, c'est dangereux" (cohérence logique)
- `delaiAnswers` (77/91/94) : suppression de "le jour même" garanti sans caveat → "généralement sous 24h, selon disponibilités"
- `delaiExtra` (77/91/94) : même correction + "nos équipes/techniciens" → "notre technicien" (entreprise individuelle)
- `p1Options` : "garantie sous 24h" → "généralement sous 24h selon les disponibilités"

**Nouveaux exports ajoutés (fin du fichier) :**
```js
export const CHENILLES_H2_TITRES        // 5 variantes H2 section chenilles
export const CHENILLES_DANGER_PREFIXE   // 5 variantes préfixe danger chenilles
export const DERAT_H2_TITRES            // 5 variantes H2 section dératisation
export const DERAT_APPROCHE_PHRASES     // 5 arrow functions (city) => string
export const MAILLAGE_TITRES            // 5 arrow functions (city) => string
export const MAILLAGE_INTRO             // 5 arrow functions (city) => string
```

#### `src/components/SmartLocalContent.js`
- Import des 6 nouveaux exports ci-dessus
- Variables calculées (après `saviezVous`) :
  ```js
  const chenillesH2 = CHENILLES_H2_TITRES[cityHash % ...]
  const chenillesDangerPrefixe = CHENILLES_DANGER_PREFIXE[(cityHash + 3) % ...]
  const deratH2 = DERAT_H2_TITRES[(cityHash + 1) % ...]
  const deratApproche = DERAT_APPROCHE_PHRASES[(cityHash + 4) % ...](primaryCity)
  const maillageTitle = MAILLAGE_TITRES[(cityHash + 2) % ...](primaryCity)
  const maillageIntro = MAILLAGE_INTRO[(cityHash + 5) % ...](primaryCity)
  ```
- Section chenilles : H2 → `{chenillesH2}`, box danger préfixée par `{chenillesDangerPrefixe}`
- Section dératisation : H2 → `{deratH2}`, paragraphe "approche complète" → `{deratApproche}`
- Section maillage : titre → `{maillageTitle}` (avec guard isMultiCity), nouveau paragraphe `{maillageIntro}`

#### `src/data/locationData.js`
**+250 villes enrichies en deux vagues :**

Vague 1 (+100 villes, commit `a0f83f5`) :
- 19 villes Val-de-Marne (94) : Bry-sur-Marne, Chennevières, Chevilly-Larue, Gentilly, L'Haÿ-les-Roses, La Queue-en-Brie, Le Perreux, Le Plessis-Trévise, Mandres-les-Roses, Marolles-en-Brie, Noiseau, Ormesson, Périgny, Saint-Mandé, Saint-Maurice, Santeny, Valenton, Villiers-sur-Marne, Maisons-Alfort
- 31 villes Essonne (91) : Gif-sur-Yvette, Bures-sur-Yvette, Bièvres, Crosne, Épinay-sous-Sénart, Fleury-Mérogis, Bruyères-le-Châtel, Ballancourt, Boutigny-sur-Essonne, Boussy-Saint-Antoine, Forges-les-Bains, Angerville, Briis-sous-Forges, Dourdan, Égly, Étiolles, Chamarande, Courances, Courson-Monteloup, Bouray-sur-Juine, Breuillet, Champlan, Brières-les-Scellés, Fontenay-lès-Briis, Courcouronnes, Bouville, Boissy-sous-Saint-Yon, Champcueil, Cheptainville, Méréville, D'Huisson-Longueville
- 50 villes Seine-et-Marne (77) : Bussy-Saint-Georges, Bussy-Saint-Martin, Bailly-Romainvilliers, Chessy, Barbizon, Bois-le-Roi, Bourron-Marlotte, Annet-sur-Marne, Chanteloup-en-Brie, Chaumes-en-Brie, Cesson, Chartrettes, Châtres, Bray-sur-Seine, Château-Landon, Carnetin, Chalifert, Changis-sur-Marne, Chailly-en-Bière, Chailly-en-Brie, Achères-la-Forêt, Arbonne-la-Forêt, Bagneaux-sur-Loing, Blandy, Collégien, Crécy-la-Chapelle, Couilly-Pont-aux-Dames, Brou-sur-Chantereine, Boutigny, Champagne-sur-Seine, Armentières-en-Brie, Boissise-le-Roi, Bombon, Rebais, Beaumont-du-Gâtinais, Cély, Baby, Béton-Bazoches, Champcenest, Conches-sur-Gondoire, Coubert, Charmentray, Avon, Beautheil-Saints, Bernay-Vilbert, Cerneux, Chauconin-Neufmontiers

Vague 2 (+150 villes, commit `66e80c1`) :
- 79 villes 77 : Lognes, Émerainville, Croissy-Beaubourg, Chevry-Cossigny, Villeneuve-le-Comte, Villeneuve-Saint-Denis, Lieusaint, Nandy, Le Mée-sur-Seine, Saint-Fargeau-Ponthierry, Dammartin-en-Goële, Othis, Moussy-le-Neuf, Monthyon, Varreddes, Germigny-l'Évêque, Condé-Sainte-Libiaire, Esbly, Lésches, Lizy-sur-Ourcq, Saint-Soupplets, Tournan-en-Brie, Liverdy-en-Brie, Presles-en-Brie, Lésigny, Férolles-Attilly, Grisy-Suisnes, Évry-Grégy-sur-Yerre, La Ferté-sous-Jouarre, Jouarre, Mormant, Maincy, Nanteuil-sur-Marne, Saâcy-sur-Marne, Méry-sur-Marne, Héricy, Samois-sur-Seine, Thomery, Veneux-les-Sablons, Montigny-sur-Loing, Grez-sur-Loing, Saint-Pierre-lès-Nemours, Souppes-sur-Loing, Lorrez-le-Bocage, Quincy-Voisins, Saint-Germain-sur-Morin, Magny-le-Hongre, Serris, Pontcarré, La Ferté-Gaucher, Pringy, Saint-Mammès, Vernou-la-Celle-sur-Seine, Vert-Saint-Denis, Maisoncelles-en-Brie, Voulangis, Compans, Le Mesnil-Amelot, Nanteuil-lès-Meaux, Mareuil-lès-Meaux, Penchard, Crégy-lès-Meaux, Gouvernes, Thorigny-sur-Marne, Précy-sur-Marne, Fresnes-sur-Marne, Guermantes, Crouy-sur-Ourcq, Coulombs-en-Valois, Saint-Cyr-sur-Morin, Orly-sur-Morin, Pommeuse, Faremoutiers, Verneuil-l'Étang, Guignes, Fontenay-Trésigny, Neufmoutiers-en-Brie, La Houssaye-en-Brie
- 71 villes 91 : Morangis, Morsang-sur-Orge, Villabé, Lisses, Saint-Pierre-du-Perray, Vigneux-sur-Seine, Saint-Michel-sur-Orge, Saintry-sur-Seine, Saint-Germain-lès-Corbeil, Morsang-sur-Seine, Tigery, Ollainville, Villiers-sur-Orge, Saint-Vrain, Itteville, Linas, Longpont-sur-Orge, Leuville-sur-Orge, Marcoussis, Verrières-le-Buisson, La Ville-du-Bois, Nozay, La Norville, Saint-Germain-lès-Arpajon, Villebon-sur-Yvette, Villejust, Orsay, Gometz-la-Ville, Saulx-les-Chartreux, Villemoisson-sur-Orge, Vert-le-Grand, Vert-le-Petit, Auvers-Saint-Georges, Saint-Chéron, Mennecy, Milly-la-Forêt, Moigny-sur-École, Oncy-sur-École, Lardy, Janville-sur-Juine, Varennes-Jarcy, Quincy-sous-Sénart, Paray-Vieille-Poste, Soisy-sur-Seine, La Ferté-Alais, Guigneville-sur-Essonne, Prunay-sur-Essonne, Nainville-les-Roches, Torfou, Écharcon, Janvry, Saint-Escobille, Sermaise, Igny, Vayres-sur-Essonne, Maisse, Fontaine-la-Rivière, Auvernaux, Le Coudray-Montceaux, Soisy-sur-École + 11 autres

**Total enrichi après session 20 mai : ~371 villes sur 627 pages (59%)**
**Restant sans données enrichies : ~256 pages**

---

## Session du 20 mai 2026 (suite) — Couverture complète

### Objectif
Enrichir les ~297 pages villes restantes dans `locationData.js` (les 256 estimées + quelques autres non comptabilisées).

### Fichiers modifiés

#### `src/data/locationData.js`
**+289 villes ajoutées en 6 sous-lots (a→y) :** couverture désormais 627/627 pages.
- Lot 1 (a-b) : abbeville-la-riviere → bussieres (37 entrées)
- Lot 2 (c-f) : cannes-ecluse → fromont-guercheville (62 entrées)
- Lot 3 (g-l) : germigny-sous-coulombs → luzancy (61 entrées)
- Lot 4 (m-p) : maison-rouge → pussay (46 entrées)
- Lot 5a (r-s) : rampillon → saint-sulpice-de-favieres (37 entrées)
- Lot 5b (s-y) : sainte-aulde → yebles (46 entrées)

**Total locationData.js après session : ~17 477 lignes, 627/627 pages enrichies.**

### Points d'attention
- 7 slugs non-villes ignorés : essonne-91, seine-et-marne-77, seine-saint-denis-93, val-de-marne-94, paris-75, sens-89100, villeneuve-sur-yonne-89500
- `prix_zone` : `"standard"` (suburban/semi-urbain) ou `"périphérique"` (rural isolé)
- `temps_intervention` : `"sous 24h"` (majorité) ou `"24-48h"` (zones très isolées)
- Commit : `97e48d8` — poussé sur `master`

### Merge et déploiement
- Branche `fix/indexation-issues` mergée dans `master`
- Vercel : mettre le Node.js en version 22.x dans les paramètres du projet (package.json engines exige 22.x)

### Architecture rappel
- `locationData.js` : chaque entrée a `quartiers`, `lieux_notables`, `problematiques_locales`, `interventions_types`, `temps_intervention`, `prix_zone`, `saisonnalite`
- Clé = slug de la page : ex. `"bussy-saint-georges-77600"`
- Insertion : avant le `};` final du `export const locationData = {`
- Les pages sans entrée dans locationData utilisent `createDefaultLocationData()` — pas de crash

---

## Session du 20 mai 2026 (suite 2) — Lighthouse, OOM, varierPhrase, varierSaisonnalite

### Objectifs
1. Corriger crash OOM webpack (`pnpm dev` — locationData.js 17 K lignes)
2. Corriger "no screenshot available" sur Vercel (X-Frame-Options)
3. Corriger `temps_intervention` affichant des délais de trajet au lieu de délais d'intervention
4. Améliorer Lighthouse accessibilité (89 → 95) PC et mobile
5. Réduire davantage la duplication de contenu entre pages : `varierPhrase` + `varierSaisonnalite`

### Fichiers modifiés

#### `vercel.json`
- **Suppression de `X-Frame-Options: SAMEORIGIN`** (bloquait le service de screenshot de Vercel, domaine différent)
- **Ajout `NODE_OPTIONS: --max-old-space-size=6144`** dans le bloc `env` (build Vercel)

#### `package.json`
- Scripts `dev`, `dev-fast-https`, `cleandev`, `mobile` : ajout `NODE_OPTIONS='--max-old-space-size=6144'` en préfixe
- Fix OOM webpack avec le fichier locationData.js de 17 K lignes

#### `src/data/locationData.js`
- **85 entrées `temps_intervention` corrigées** : valeurs "15-25 minutes", "20-30 minutes", "25-35 min", etc. → `"sous 24h"`
  - Ces valeurs stockaient un temps de trajet routier affiché à tort comme délai d'intervention dans la FAQ
  - Distribution finale : 597× `"sous 24h"`, 56× `"24-48h"`

#### `src/utils/contentVariations.js`
**`varierPhrase(phrase, cityHash)`** (export) :
- Constante `VARIANTS_MAP` (non exportée) : 5 pools de phrases récurrentes dans locationData
  - `residential_problem` : "problèmes de guêpes dans les zones résidentielles" → 8 variantes
  - `combles` : "construction de nids dans les combles" → 8 variantes
  - `destruction_residentielle` : "destruction des nids dans les zones résidentielles" → 8 variantes
  - `traitement_combles` : "traitement des nids dans les combles" → 8 variantes
  - `frelons_jardins` : "présence de frelons asiatiques dans les jardins" → 8 variantes
- Fonction : boucle sur VARIANTS_MAP, retourne la variante si la phrase contient le mot-clé, sinon retourne la phrase originale

**`varierSaisonnalite(saisonnalite, cityHash)`** (export) :
- Constante `SAISONNALITE_PREFIXES` (non exportée) : 12 pools de préfixes temporels × 8 variantes chacun
  - Ex. "Pic de juin à septembre" → "Activité maximale de juin à septembre", "Saison haute de juin à …", etc.
- Fonction : identifie le préfixe correspondant, le remplace par la variante hash, conserve le suffixe géographique

#### `src/components/SmartLocalContent.js`
- Import : `varierPhrase`, `varierSaisonnalite`
- Ajout après `cityHash` : `const vphr = (phrase) => varierPhrase(phrase, cityHash);`
- **Bloc `interventionTypes`** : `vphr(intervention)` sur chaque pill
- **Liste à puces des 4 `localProblems`** (tous les items, pas seulement le premier) :
  ```jsx
  <div style={{ textAlign: 'center' }}>
    <ul style={{ display: 'inline-block', textAlign: 'left', margin: '8px 0 16px 0', paddingLeft: '20px', lineHeight: '1.9' }}>
      {uniqueContent.localProblems.map((prob, index) => (
        <li key={index} style={{ marginBottom: '4px' }}>{vphr(prob)}</li>
      ))}
    </ul>
  </div>
  ```
  - Pattern `inline-block` + `textAlign: 'left'` dans wrapper `textAlign: 'center'` pour centrer la liste tout en alignant les puces
- **Conseil box** : utilise `localProblems[1]` (au lieu de `[0]`) passé par `vphr()`
- **saisonnaliteDetail** : applique `varierSaisonnalite(uniqueContent.saisonnaliteDetail, cityHash)`

#### `src/components/ClientOnly.js` (nouveau fichier)
```js
import { useState, useEffect } from 'react';
export default function ClientOnly({ children, fallback = null }) {
  const [mounted, setMounted] = useState(false);
  useEffect(() => { setMounted(true); }, []);
  return mounted ? children : fallback;
}
```

#### `src/components/ZoomClient.js` (nouveau fichier)
```js
import React, { useState, useEffect } from 'react';
import Zoom from 'react-medium-image-zoom';
export default function ZoomClient({ children, zoomMargin = 40 }) {
  const [mounted, setMounted] = useState(false);
  useEffect(() => { setMounted(true); }, []);
  if (!mounted) return children;
  return <Zoom zoomMargin={zoomMargin}>{children}</Zoom>;
}
```
- Résout l'erreur `aria-owns` invalide générée côté client par `react-medium-image-zoom` v5.3.0 (l'élément modal n'existe pas au SSR)
- Utilisé dans `galerie-guepes-2.js` uniquement

#### `src/pages/galerie-guepes-2.js`
- `import Zoom` → `import ZoomClient`
- Tous les `<Zoom zoomMargin={40}>` → `<ZoomClient>`, `</Zoom>` → `</ZoomClient>`

#### `src/pages/index.js`
**Lighthouse accessibilité (89 → 95) :**
- 4 `<h3>` de cartes de services → `<p>` styled (évite le saut de niveau heading)
- Couleur lien rouge : `#e6332a` → `#b02a23` (ratio contraste 4.3:1 → 5.6:1, dépasse le seuil WCAG AA de 4.5:1)
- `<XIcon>` dans Footer : `aria-hidden="true"` ajouté (le texte 𝕏 ne devait pas être annoncé, aria-label sur le `<a>` suffit)
- Suppression des `aria-label` redondants sur les liens téléphone et email (le texte visible est suffisant)
- **ZoomClient supprimé de index.js** : react-medium-image-zoom génère `aria-owns` côté client pointant vers un id modal inexistant — violation ARIA non corrigeable côté SSR
- Hero image `<StaticImage>` : `loading="eager"` + `fetchpriority="high"` pour optimiser le LCP (régression 1.0s→1.7s corrigée)

#### `src/components/Footer/Footer.js`
- `<XIcon aria-hidden="true">𝕏</XIcon>` : `aria-hidden` pour exclure le symbole typographique du nom accessible du lien
- Suppression `aria-label` sur lien téléphone et email (texte visible suffit)

### Résultats Lighthouse après session
| Métrique | PC avant | PC après | Mobile avant | Mobile après |
|---|---|---|---|---|
| Accessibilité | 89 | 95 | 89 | 95 |
| TBT | — | — | 130ms | 60ms |
| LCP | ~1.0s | ~1.7s* | — | — |

*Régression LCP PC due à suppression accidentelle de `fetchpriority` ; corrigé en fin de session (à vérifier avec nouveau Lighthouse)

### Problèmes connus non résolus
- **React error #418** (hydration mismatch) : 7 occurrences dans Lighthouse, source bundle `app-5a94f17d` — probablement `gatsby-plugin-offline` ou service worker. Non bloquant pour le SEO.
- **TTI mobile 9.3s / score 31** : principalement dû aux scripts GTM/GA tiers. Non corrigeable sans retirer l'analytics.
- **Third-party cookies** : GTM/GA — non corrigeable.

### Commits de cette session
- Corrections accessibilité + varierPhrase/varierSaisonnalite : sur `master`
- Dernier commit connu : `12e89a3`

---

## Ce qui N'a PAS changé (invariants)

- La logique de zone tarifaire (80€ Châtelet, 90€ zone1, 110€ zone2, 130€ zone3)
- La structure des redirections dans `vercel.json`
- Le système de hash déterministe `cityHash` pour la sélection des variants
- Les fichiers des ~627 pages individuelles (wrappers minimaux, inchangés)
- `gatsby-node.js` — inchangé

---

## Commandes utiles

```bash
npm run dev-fast          # développement rapide (sans pages villes)
npm run restore-location-pages  # avant build prod
npm run build             # build complet (restaure automatiquement les pages)
npm run pages-status      # vérifier si les pages sont cachées ou non
```
