# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

---

## Ce qu'est ce dépôt

Ce dépôt est un **boilerplate de configuration** (pas de code Next.js). Il contient uniquement les fichiers de contexte nécessaires pour qu'un outil IA (Cursor) génère un projet Next.js complet. Le code Next.js est généré par Cursor à partir de ces fichiers, pas ici.

---

## Stack technique

| Outil | Rôle |
|---|---|
| Next.js App Router | Framework frontend |
| TypeScript + Tailwind CSS | Langage et styling |
| Sanity (headless CMS) | Contenu éditable par le client via GROQ / next-sanity |
| Vercel | Hébergement + déploiement automatique via GitHub |
| Figma Make | Génération de maquettes |
| WPGraphQL | Récupération de contenu depuis un WordPress existant (optionnel) |
| Resend | Envoi d'emails transactionnels depuis les formulaires (optionnel) |
| Supabase | Stockage des soumissions de formulaires (optionnel) |

---

## Fichiers de ce boilerplate

| Fichier | Rôle |
|---|---|
| `.cursorrules` | Instructions Cursor — conventions, SEO, Sanity, redirections (racine, lu automatiquement) |
| `README.md` | Guide de démarrage (racine) |
| `config/architecture.yaml` | Arborescence du site : pages, slugs, types, templates, métadonnées SEO — **protégé** |
| `config/navigation.yaml` | Structure du menu principal (header) — pages, ordre, niveaux — **protégé** |
| `config/navigation-footer.yaml` | Liens du footer — **protégé** |
| `config/context-stack.md` | Documentation détaillée de la stack et du workflow de production — **protégé** |
| `config/redirects.json` | Redirections 301 (vide au départ, format `[{"source","destination","permanent"}]`) — **protégé** |

> Le dossier `config/` est protégé — Cursor ne doit jamais en supprimer ou écraser le contenu.

> ⚠️ Le fichier `cursorrules` (sans point, à la racine) est une copie obsolète et incomplète de `.cursorrules` — elle date d'avant les sections formulaires de contact, CSS custom et export de contenu WordPress. Ne jamais s'y référer ni la mettre à jour ; `.cursorrules` est la seule source de vérité.

---

## Workflow en 8 phases (voir `config/context-stack.md` pour le détail complet)

| Phase | Étape | Validation |
|---|---|---|
| 1 | Initialisation — dupliquer le boilerplate, `npx create-next-app@latest`, `npm run dev` | `localhost:3000` s'affiche |
| 2 | Git & GitHub — branches `main/preprod/dev` + Vercel + **DNS Resend** *(si formulaire de contact prévu : ajouter et vérifier le domaine dans Resend maintenant, la propagation DNS peut prendre du temps)* | Repo GitHub accessible, domaine vérifié dans Resend |
| 3 | WPGraphQL *(optionnel)* — plugin WordPress, test requêtes | GraphiQL répond correctement |
| 3B | **Vérification locale** — confirmer `localhost:3000` + données WPGraphQL visibles | Navigateur OK, données remontent |
| 4 | Arborescence — personnaliser `architecture.yaml`, `/admin/arborescence`, redirections | Structure validée |
| 4B | **Validation visuelle** — génération sitemap + `/plan-du-site`, vérifier URLs et hiérarchie | Sitemap XML valide, slugs définitifs |
| 4C | **Récupération contenu & médias** *(si WordPress)* — `download-medias` (images) + `export-content` (JSON dans `config/content/`), test page et article | Build OK sans `WORDPRESS_GRAPHQL_URL` |
| 5 | Maquettes & code — Figma Make / V0 → Cursor, SEO, breadcrumb | `npm run build` passe |
| 5B | **Formulaire de contact** *(si applicable)* — Supabase (créer table `contact_submissions`) + API route `/api/contact` + page contact-merci + variables Vercel | Email reçu, entrée visible dans Supabase |
| 6 | Sanity — schéma, contenu, aucun texte en dur dans le JSX | Contenu éditable depuis Studio |
| 7 | Gestion des médias — `/admin/medias`, métadonnées Sanity, remplacement d'images | Images OK, alts depuis Sanity |
| 8 | Déploiement — Vercel *(si pas encore fait)*, client valide sur `preprod`, merge `main` | Site en production |
| Post-8 | **Documentation client** — générer le document de prise en main adapté aux fonctionnalités du projet (Sanity, formulaire, blog, etc.) | Document livré au client |

Chaque phase a une checklist de validation dans `config/context-stack.md`.

### Fournir à Cursor à chaque session
1. `.cursorrules` — chargé automatiquement
2. `config/architecture.yaml`
3. `config/context-stack.md`

### Variables d'environnement (.env.local — ne jamais commiter)
```
NEXT_PUBLIC_SANITY_PROJECT_ID=
NEXT_PUBLIC_SANITY_DATASET=production
SANITY_API_TOKEN=
WORDPRESS_GRAPHQL_URL=https://tonsite-wordpress.com/graphql
# Formulaire de contact (optionnel — Phase 5B)
RESEND_API_KEY=
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
CONTACT_EMAIL=contact@monsite.fr
NEXT_PUBLIC_RECAPTCHA_SITE_KEY=
RECAPTCHA_SECRET_KEY=
```

### Commandes
```bash
npm run dev          # Serveur local → localhost:3000
npm run build        # Build de production
npm run lint         # Lint ESLint
npx ts-node scripts/download-medias.ts  # Migration médias WordPress (une seule fois, Phase 4C)
npm run export-content                  # Export contenu WordPress → config/content/ (une seule fois, Phase 4C)
```

---

## Architecture du projet Next.js généré

```
/
├── /app                        # Pages (App Router)
│   ├── sitemap.ts              # Génère sitemap.xml depuis architecture.yaml
│   └── /plan-du-site/          # Page HTML du plan de site
├── /components
│   ├── Breadcrumb.tsx          # Fil d'ariane (toutes pages sauf homepage)
│   └── /cards/                 # CardArticle, CardService, CardCategorie, CardRecherche, CardJob
│   └── /templates/             # Templates de pages (voir liste ci-dessous)
├── /lib
│   └── sitemap-utils.ts        # getSitemapPages(), generateSitemapXml(), getPriority(), FALLBACK_LASTMOD
├── /sanity                     # Schémas et config Sanity
├── /config
│   ├── architecture.yaml       # Source de vérité pour la structure
│   ├── redirects.json          # Redirections 301 déclarées dans next.config.js
│   ├── medias-map.json         # Généré par le script de migration médias
│   └── /content/               # Généré par export-content (WordPress uniquement)
├── /scripts
│   └── download-medias.ts      # Script de migration médias WordPress
└── /public/images/{pages,articles,ui}
```

---

## architecture.yaml — règles clés

**Chaque page doit déclarer explicitement ces champs :**
```yaml
published: true/false          # false = page non générée
noindex: true/false            # true = <meta robots noindex,nofollow>
excludeFromSitemap: true/false # true = exclue du sitemap.xml et /plan-du-site
lastmod: "AAAA-MM-JJ"          # date de création/dernière modif — obligatoire pour toute nouvelle page
```

**Sitemap — lastmod et priorité :**
- `lastmod` obligatoire sur chaque nouvelle page ; sans lui, fallback sur `FALLBACK_LASTMOD` (à remettre à jour à chaque déploiement significatif de contenu) — sinon Google ne distingue plus les pages récentes des anciennes
- Priorité calculée automatiquement par `getPriority()` dans `/lib/sitemap-utils.ts` (jamais à la main) : homepage 1, hub niveau 1 0.9, archive 0.8, détail niveau 2 0.7, niveau 3+ 0.6, article 0.5
- Après un ajout de pages en masse : soumettre le sitemap dans Search Console et forcer l'indexation des nouvelles pages hub via l'outil d'inspection d'URL

**Séparation YAML / Sanity :**
- YAML = source de vérité pour la **structure** (`type`, `template`, `children`, `archive`)
- Sanity = source de vérité pour le **contenu et les décisions éditoriales** (`published`, `noindex`, `slug`, `metaTitle`, `metaDescription`)
- En cas de conflit : Sanity a la priorité sur YAML pour les champs éditables

**Pour supprimer une page :** passer `published: false` + ajouter la redirection dans `redirects.json` (ne jamais supprimer le bloc YAML)

**Pages avec `noindex: true` + `excludeFromSitemap: true` par défaut :** `contact-merci`, `404`, toute page de remerciement/confirmation

**Types de pages :** `homepage`, `page`, `archive`, `article`

**Templates disponibles :** `default`, `homepage-template`, `service-template`, `archive-template`, `article-template`, `contact-template`, `contact-merci-template`, `a-propos-template`, `mentions-legales-template`, `confidentialite-template`, `404-template`, `plan-du-site-template`, `search-template`, `jobboard-template` (optionnel)

---

## Conventions de code (App Router)

- App Router uniquement (`/app` — pas de `/pages`)
- Composants TypeScript `.tsx`, un composant par fichier
- Nommage : PascalCase pour les composants, kebab-case pour les fichiers
- Imports absolus avec alias `@/`
- `<Image>` de Next.js partout — jamais de `<img>` brute
- Images dans `/public/images/` nommées en kebab-case avec mots-clés SEO

## Qualité du code généré

**HTML / JSX**
- Structure HTML la plus simple possible
- HTML sémantique, pas de divs inutiles
- Pas de wrappers supplémentaires sauf si strictement nécessaire
- Pas de classes CSS redondantes
- Composants JSX sous 50 lignes si possible

**Refactorisation**
- Ne jamais changer le comportement existant lors d'une refactorisation
- Ne jamais supprimer une fonctionnalité sans validation explicite
- Si un changement peut avoir un impact, le signaler avant d'agir

**Général**
- Ne commenter le code que si la logique est complexe ou non évidente
- Toujours proposer la solution la plus simple avant d'envisager une solution complexe

**Performance**
- Pas d'imports inutilisés
- Écrire du code tree-shaking friendly (imports nommés)
- Lazy load les composants lourds avec `next/dynamic`

**Maintenabilité**
- Noms de variables et fonctions explicites et descriptifs
- Pas de magic numbers — utiliser des constantes nommées
- Séparer logique et présentation avec des custom hooks

**Typage**
- Props de chaque composant typées avec une interface TypeScript dédiée

## CSS & classes — système de classes réutilisables

- Tailwind = style ponctuel. Tout motif répété (espacement de section, titre, carte, bouton récurrent) → classe custom réutilisable dans `app/globals.css` sous `@layer components` (`@apply`), en `snake_case`
- Familles de référence : `row_pad` / `row_pad_top` / `row_pad_bottom` (padding de section), `titre_1` / `titre_2` / `titre_3` (niveaux de titre) ; famille dédiée si charte différente (ex. `titre_article_1`)
- Décorations de titre (soulignement, puce, icône) → `::before`/`::after` dans le CSS, jamais un `<span>`/`<div>` ajouté juste pour l'effet
- Interdit : variantes quasi-identiques d'une classe existante (`titre_2_bis`, `row_pad_2`) — réutiliser ou créer une seule classe pérenne. Détail complet : `config/context-stack.md` §5.5 et `.cursorrules`

## Fidélité de montage — recréer une page à l'identique

- S'applique à toute page montée à partir d'une référence à reproduire (maquette Figma/V0, capture d'écran, page existante à migrer)
- Avant de coder : analyser toute la source (sections, ordre, textes, interactions, responsive) et s'appuyer sur le découpage/les classes des pages du même `type`/`template` déjà montées — ne pas coder au fil de l'eau
- Avant de déclarer terminé : repasser source et résultat côte à côte section par section (pas une simple impression visuelle), vérifier la cohérence avec les pages du même type, corriger tout écart plutôt que de le noter pour plus tard, et signaler explicitement les écarts volontaires. Détail complet : `config/context-stack.md` §5.9 et `.cursorrules`

---

## SEO — règles obligatoires sur chaque page

- Une seule `<h1>` par page, hiérarchie stricte H1→H2→H3
- `generateMetadata()` pour injecter `metaTitle` (50-60 car.) et `metaDescription` (140-160 car.) depuis Sanity ou le YAML en fallback
- JSON-LD sur chaque page (minimum `WebPage` ou `Service` ; `FAQPage` si FAQ ; `BreadcrumbList` sur toutes les pages sauf homepage)
- Slugs en minuscules, kebab-case, sans caractères spéciaux
- `noindex` → injecter `<meta name="robots" content="noindex, nofollow">` via `generateMetadata()`

---

## Sanity — règle absolue

Si Sanity est connecté, **tout le contenu visible doit être éditable dans Sanity Studio** : titres, paragraphes, boutons, images, liens, navigation, footer, mentions légales, métadonnées SEO, textes alt des images. Aucun texte éditorial en dur dans le JSX. Les balises `alt` ne s'écrivent jamais en dur — elles viennent toujours de Sanity.

Champs minimum par page dans Sanity : `titre`, `contenu`, `metaTitle`, `metaDescription`, `slug`, `image principale avec alt`.

---

## Formulaires de contact (Phase 5B — optionnel)

Workflow : `Formulaire (Next.js) → /api/contact → Resend (email) + Supabase (stockage)`

**Prérequis à faire tôt (Phase 2) :**
- Créer un compte Resend, ajouter et vérifier le domaine (enregistrements DNS TXT + CNAME chez le registraire)
- La vérification DNS peut prendre plusieurs heures — à faire dès que le domaine est connu

**Mise en place (Phase 5B) :**
- Créer un compte Supabase, créer la table `contact_submissions` (colonnes : `id`, `name`, `email`, `message`, `created_at`)
- `npm install resend @supabase/supabase-js react-google-recaptcha-v3`
- Créer `/app/api/contact/route.ts` — vérification reCAPTCHA v3 d'abord, puis envoi Resend + insert Supabase en parallèle (`Promise.all`)
- Clés reCAPTCHA à créer sur google.com/recaptcha/admin (ajouter `localhost` ET le domaine de prod)
- Page contact-merci : `noindex: true`, `excludeFromSitemap: true`, redirection 301 si slug change
- Ajouter les **6 variables** dans Vercel > Settings > Environment Variables (pas seulement dans `.env.local`)

**Limites plan gratuit :**
- Resend : 100 emails/jour (bloqué si dépassé, pas de file d'attente)
- Supabase : 500 000 lignes, 50 000 requêtes/mois

---

## Fonctionnalités générées automatiquement

**Fil d'ariane (`/components/Breadcrumb.tsx`)** : présent sur toutes les pages sauf homepage, généré depuis la hiérarchie de `architecture.yaml`, avec JSON-LD `BreadcrumbList`.

**Sitemap** : `/app/sitemap.ts` lit `architecture.yaml` via `/lib/sitemap-utils.ts` et génère `sitemap.xml` automatiquement à chaque déploiement Vercel.

**Redirections** : déclarées dans `/config/redirects.json`, importées dans `next.config.js`. Toute suppression de page ou changement de slug génère une redirection 301.

**Interfaces admin (local uniquement, ne pas déployer en prod) :**
- `localhost:3000/admin/arborescence` : tri et organisation des pages (drag & drop, gestion des statuts, export vers `architecture.yaml`)
- `localhost:3000/admin/medias` : bibliothèque média avec édition des métadonnées via Sanity

---

## WPGraphQL (migration depuis WordPress)

Utilisé uniquement si le projet migre depuis un WordPress existant. Le plugin WPGraphQL doit être installé sur le WordPress **avant** de coder. Ne jamais utiliser la REST API WordPress.

- Stocker l'URL GraphQL dans `.env.local` : `WORDPRESS_GRAPHQL_URL`
- Récupérer uniquement les métadonnées légères pour l'arborescence (title, slug, metaTitle, metaDescription, status)
- Mapper `status` WordPress → `published` YAML : `publish` → `true`, `draft`/`private` → `false`
- Ne jamais écraser `architecture.yaml` automatiquement sans confirmation explicite

---

## Git — branches et configuration

- `main` → production (connectée à Vercel)
- `preprod` → validation client (URL de prévisualisation Vercel)
- `dev` → développement en cours
- Flux : `dev` → `preprod` (validation client) → `main` (déploiement production)

**Avant le premier commit — configurer l'email Git :**
```bash
git config user.email "123456789+tonusername@users.noreply.github.com"
git config user.name "tonusername"
```
L'email doit correspondre au compte GitHub (noreply ou email personnel via GitHub → Settings → Emails). Sans ça, Vercel bloque les déploiements avec le statut "Blocked".
