# QA Audit — seo-insight-Web
**Date :** 21 juin 2026 · **Méthode :** 6 agents parallèles (TS, i18n, SEO, a11y, blog/docs, deps)  
**Total :** 82 problèmes — 7 critical · 33 high · 26 medium · 16 low

---

## 🎨 Frontend Agent

> Composants UI, accessibilité (WCAG), styles, animations, i18n côté affichage.

### CRITICAL

| # | Fichier | Problème |
|---|---------|---------|
| F-C1 | `contact/_components/contact-form.tsx` | Page contact + formulaire 100 % anglais hardcodé pour les visiteurs FR : labels de champs, messages de validation Zod, texte de confirmation |

### HIGH

| # | Fichier | Problème |
|---|---------|---------|
| F-H1 | `app/[lang]/layout.tsx:72` | `<Navbar>` et `<Footer>` placés **à l'intérieur du `<main>`** — landmark sémantique incorrect, footer ne remplit pas le rôle `contentinfo` |
| F-H2 | Layout global | **Skip link absent** — WCAG 2.4.1 Niveau A : les utilisateurs clavier traversent toute la navbar à chaque page |
| F-H3 | `components/sections/faqs.tsx:49` | `<button><h3>` → **HTML invalide** (heading à l'intérieur d'un élément interactif) ; comportement AT imprévisible |
| F-H4 | `components/sections/faqs.tsx` | `aria-expanded` absent sur le bouton accordéon FAQ → les AT ne communiquent pas l'état ouvert/fermé |
| F-H5 | `site-search.tsx:90` + `docs-search.tsx:83` | Inputs de recherche sans `aria-label` (le `placeholder` seul n'est pas un label valide WCAG 1.3.1) |
| F-H6 | `components/sections/faqs.tsx` | `AnimatePresence` importé mais **absent du JSX** → l'animation `exit` (height collapse) ne se déclenche jamais ; bug visuel silencieux |
| F-H7 | `blog/page.tsx` | "Master search. / Win the rankings." et le mot "views" hardcodés EN dans le JSX — absents du dictionnaire fr.json |
| F-H8 | Services (4 pages) | Tout le contenu visible (`const content`) hardcodé EN sous des URLs `/fr/services/*` |
| F-H9 | `about/page.tsx` | Stub texte EN indexé sans `noindex` |

### MEDIUM

| # | Fichier | Problème |
|---|---------|---------|
| F-M1 | `components/shared/language-switcher.tsx:100` | `aria-label="Change language (current FR)"` hardcodé EN — visible aux lecteurs d'écran francophones |
| F-M2 | `components/shared/language-switcher.tsx:108` | Navigation clavier **flèches Haut/Bas absente** dans le menu (`role="menu"` sans comportement Arrow Keys APG) |
| F-M3 | `components/sections/pricing.tsx` | Prix animé sans `aria-live="polite"` → changement mensuel/annuel invisible pour les AT |
| F-M4 | `components/sections/faqs.tsx` | `aria-controls` + `id` absents → bouton FAQ non lié sémantiquement au panneau réponse |
| F-M5 | `components/layout/footer.tsx:89` | Input newsletter + bouton `type="submit"` hors d'un `<form>` → Enter-to-submit cassé, soumission impossible |
| F-M6 | Multiple composants | Icônes décoratives sans `aria-hidden="true"` : `IconArrowRight` (cta), `IconMinus`/`IconPlus` (faqs), SVG blog (`MiniItem`), `MoonIcon`/`SunIcon` (mode-toggle) |
| F-M7 | `components/shared/mode-toggle.tsx` | `aria-pressed` absent sur le bouton de thème → AT ne communique pas l'état dark/light |
| F-M8 | `blog/[category]/page.tsx:86` | Grille vide sans message d'état quand une catégorie n'a aucun post traduit en FR |
| F-M9 | `components/sections/faqs.tsx:3` | `useRef` importé inutilement |
| F-M10 | 14 fichiers | `import React from "react"` superflu (React 17+ JSX transform automatique) |

### LOW

| # | Fichier | Problème |
|---|---------|---------|
| F-L1 | `components/layout/footer.tsx:147` | `style={{ transform: " rotateX(60deg) " }}` — espaces parasites dans la valeur CSS |
| F-L2 | `components/sections/hero/landing-images.tsx:39` | Alt text `"Demo 1 for agenforce template"` non mis à jour (héritage du template source) |
| F-L3 | `components/shared/logo-cloud.tsx:33` | `<section>` sans `aria-labelledby` → n'apparaît pas comme région de navigation dans l'arbre AT |
| F-L4 | `components/docs/docs-nav.tsx:35` | Active-state en exact-match uniquement → fragile si Next.js ajoute un hash ou query string |

---

## ⚡ Fullstack Agent

> Routing, i18n, SEO/metadata, pipeline blog/docs, Server Components, cache.

### CRITICAL

| # | Fichier | Problème |
|---|---------|---------|
| FS-C1 | `app/sitemap.ts` + pas de `middleware.ts` | Root `/` absent du sitemap ; pas de redirect `/` → `/en`/`/fr` |
| FS-C2 | `blog/page.tsx` + `blog/[category]/page.tsx` | `generateMetadata` sans `alternates` → canonical et hreflang `<link>` on-page absents (incohérent avec le sitemap) |
| FS-C3 | `lib/blog/posts.ts:29` | Pas de `try/catch` sur la lecture individuelle des fichiers MDX → crash module-level = **500 sur toutes les routes blog** |
| FS-C4 | Routes `/blog` et `/docs` | Aucun `error.tsx` nulle part → erreur de rendu = 500 brut sans récupération possible |

### HIGH

| # | Fichier | Problème |
|---|---------|---------|
| FS-H1 | `proxy.ts` | Fichier nommé `proxy.ts` au lieu de `middleware.ts` → **la logique i18n de redirection n'est JAMAIS exécutée** par Next.js |
| FS-H2 | `tsconfig.json` | `next-env.d.ts` absent (projet jamais buildé) → 47 erreurs TS bloquantes ; résolu par `bun dev` |
| FS-H3 | `pricing/page.tsx:11` | `PageProps<"/[lang]">` → devrait être `PageProps<"/[lang]/pricing">` |
| FS-H4 | `services/page.tsx:12` | Même problème `PageProps` incorrect |
| FS-H5 | `lib/blog/posts.ts:33` | `data as BlogPost` cast sans validation runtime → `undefined` silencieux sur champs required (`post.author.name` → TypeError en prod) |
| FS-H6 | `lib/docs/search-index.ts:70` | Heading slugué avec markdown brut → liens d'ancre cassés pour headings en `**bold**` ou `` `code` `` |
| FS-H7 | `lib/blog/toc.ts:16` | Fences tilde `~~~` non détectées → headings dans ces blocs apparaissent dans la TOC |
| FS-H8 | `components/blog/post-body.tsx:15` | `"use cache"` + composants client via `MDXRemote` — risque de compatibilité Next.js 16 non validé |
| FS-H9 | `blog/page.tsx` + `blog/[category]/page.tsx` | `generateMetadata` : title/description hardcodés EN quelle que soit la locale |
| FS-H10 | `app/[lang]/layout.tsx:37` | OG image en bare string sans `width`/`height`/`alt` → parseurs sociaux (FB, LinkedIn) peuvent rejeter l'image |
| FS-H11 | `blog/[category]/[slug]/page.tsx:62` | OG image `undefined` si pas de `seo.ogImage` en frontmatter → partages sociaux sans image |
| FS-H12 | Toutes les pages docs (MDX) | Aucun bloc `openGraph` → docs partagées sur Slack/Twitter sans image ni titre enrichi |
| FS-H13 | `components/meta/json-ld.tsx` | Schema `SoftwareApplication`/`Offer` émis sur **toutes les pages** (global), pas uniquement sur `/pricing` |
| FS-H14 | Pages services (4 pages) | Zéro JSON-LD (pas de `Service`, `BreadcrumbList`, ni `FAQPage` malgré des sections FAQ riches) |
| FS-H15 | `lib/legal/types.ts` | `LegalSection` non exportée — impossible à importer individuellement par les consumers |

### MEDIUM

| # | Fichier | Problème |
|---|---------|---------|
| FS-M1 | Tout le codebase | **Twitter Cards complètement absentes** — aucun `twitter:card`, `twitter:site`, `twitter:image` sur aucune page |
| FS-M2 | `lib/seo/pages.ts` | OG block sur toutes les pages marketing sans `type`, `siteName`, ni `images` → pas d'image sociale sur home/pricing/services/contact |
| FS-M3 | `lib/seo/structured-data.ts` | `WebSite` sans `potentialAction` → Sitelinks Searchbox Google non activable |
| FS-M4 | `lib/seo/structured-data.ts` | `BlogPosting` → préférer `Article` pour l'éligibilité aux Rich Results Google |
| FS-M5 | `lib/seo/alternates.ts:20` | Risque de trailing-slash inconsistency entre canonical emis et URLs réelles servies |
| FS-M6 | `about/page.tsx` | Page thin content indexée (texte "More about our story soon") → risque de pénalité |
| FS-M7 | `privacy/page.tsx`, `terms/page.tsx` | `description: doc.intro` probablement >155 chars → tronqué en SERP |
| FS-M8 | `app/[lang]/blog/[category]/page.tsx:84` | BreadcrumbList JSON-LD incomplète si `cat` est falsy (saut Home → Blog → Article sans catégorie) |
| FS-M9 | `docs/layout.tsx:26` | `buildDocsSearchIndex()` non memoïsé → lecture FS à chaque hot-reload en développement |
| FS-M10 | `docs-pager.tsx:21` | Trailing slash sur `pathname` → pager retourne `null` silencieusement (no previous/next links) |
| FS-M11 | `lib/blog/categories.ts` | `name`/`blurb` des catégories EN uniquement, jamais localisés — labels visibles EN pour les visiteurs FR |

### LOW

| # | Fichier | Problème |
|---|---------|---------|
| FS-L1 | `app/sitemap.ts` | Pages catégorie blog sans `lastModified` → Googlebot recrawle moins fréquemment |
| FS-L2 | `services/cms-optimization/page.tsx`, `wordpress-seo/page.tsx` | Contenu EN visible sous URL FR → mismatch hreflang détectable par Google |
| FS-L3 | `lib/seo/structured-data.ts` | JSON-LD `@context` dupliqué sur les pages blog (global + page-level) → à consolider en un seul `@graph` |
| FS-L4 | `lib/search/site-index.ts:21` | Champ `lang` absent des enregistrements intermédiaires → type non pleinement enforced (TypeScript ne détecte pas l'oubli) |
| FS-L5 | `lib/blog/toc.ts:3` | `lib/` importe un type depuis `components/` — dépendance inversée, fragile architecturalement |
| FS-L6 | `lib/blog/posts.ts:70` | `ALL_POSTS` singleton synchrone au chargement du module → cold-start non observable ni instrumenté |

---

## 🔧 Backend Agent

> Formulaires/API, sécurité, dépendances, infrastructure, configuration build.

> **✅ Statut (corrigé le 2026-06-21 — branche `fix/backend-qa-audit-2026-06-21`, mergée dans `agentroom`, build vert) : 17/18.**
> Corrigés : B-C1, B-H1, B-H2, B-H3, B-M1, B-M2, B-M3, B-M4, B-M5, B-M6, B-M7, B-L1, B-L4, B-L6.
> Investigués → conservés à raison : **B-L2** (`@next/mdx` + `next-mdx-remote` tous deux requis), **B-L3** (`shiki` est une peerDependency de `rehype-pretty-code`), **B-L7** (le `.webp` legacy est le seul logo fetchable par URL pour le JSON-LD).
> Différé : **B-L5** (`"use cache"` sur les pages marketing — risqué, voir `TODO.md`).

### CRITICAL

| # | Fichier | Problème |
|---|---------|---------|
| B-C1 | `contact/_components/contact-form.tsx:50` | `onSubmit` fait `void values; setIsSubmitted(true)` — le formulaire affiche "Message sent" **sans rien envoyer** (aucun service email connecté) |

### HIGH

| # | Fichier | Problème |
|---|---------|---------|
| B-H1 | `lib/seo/structured-data.ts` | `Organization` JSON-LD : manque `contactPoint`, `sameAs` (profils sociaux), `email` → Knowledge Panel Google dégradé |
| B-H2 | `components/meta/json-ld.tsx` | `SoftwareApplication` JSON-LD : pas de `url`, pas d'`availability`, pas de `priceValidUntil` → Google Rich Results test incomplet |
| B-H3 | `@types/mdx` dans `dependencies` | Types TypeScript en runtime deps → déplacer en `devDependencies` |

### MEDIUM

| # | Fichier | Problème |
|---|---------|---------|
| B-M1 | `.gitignore` | **Aucune ligne `.env*`** → un `.env.local` avec des secrets serait commité accidentellement |
| B-M2 | Racine du projet | **`.env.example` absent** → onboarding impossible ; variables requises : `NEXT_PUBLIC_SITE_URL`, `NEXT_PUBLIC_APP_URL`, `NEXT_PUBLIC_OP_CLIENT_ID`, `NEXT_PUBLIC_OP_API_URL`, `NEXT_PUBLIC_GA4_MEASUREMENT_ID` |
| B-M3 | `next.config.ts` | Aucun bloc `headers()` → `X-Frame-Options`, `X-Content-Type-Options`, `Referrer-Policy`, `Content-Security-Policy` absents |
| B-M4 | `contact/page.tsx:24` | Données placeholder (adresse, téléphone, email fictifs) en production et page indexable |
| B-M5 | `package.json` | `radix-ui` (umbrella) + `@radix-ui/react-slot` déclarés séparément → risque de divergence de version ; `@radix-ui/react-slot` est redondant |
| B-M6 | `docs/**` (Knip + tsconfig) | `@heroicons/react` importé dans `docs/assets/` sans être déclaré dans `package.json` → dépendance fantôme |
| B-M7 | `knip.json:6` | `"ignore": ["docs/**"]` → Knip aveugle sur les fichiers actifs du dossier `docs/` |

### LOW

| # | Fichier | Problème |
|---|---------|---------|
| B-L1 | `package.json` | Deux librairies d'icônes (`lucide-react` + `@tabler/icons-react`) — 5 icônes lucide à migrer vers Tabler puis supprimer lucide |
| B-L2 | `package.json` | Deux renderers MDX (`@next/mdx` + `next-mdx-remote`) — surface de config dupliquée, ~30 KB bundle en plus |
| B-L3 | `package.json` | `shiki` en dépendance directe alors que c'est une peer dep de `rehype-pretty-code` → risque de version mismatch |
| B-L4 | `next.config.ts:9` | `outputFileTracingIncludes` avec clé `"/**/*"` trop large → tracing MDX inutile sur toutes les routes |
| B-L5 | `next.config.ts` | `cacheComponents: true` sans directive `"use cache"` sur les pages marketing → pages statiques re-rendues par requête |
| B-L6 | `lib/seo/structured-data.ts:28` | Logo `ImageObject` sans `width`/`height` → non éligible au Google Knowledge Panel |
| B-L7 | `public/seo-insight-logo.webp` | Asset legacy (cf. TODO.md) encore référencé uniquement dans `structured-data.ts` — à remplacer par le logo SVG actuel |

---

## 📊 Récapitulatif par agent

| Agent | Critical | High | Medium | Low | Total |
|-------|----------|------|--------|-----|-------|
| 🎨 Frontend | 1 | 9 | 10 | 4 | **24** |
| ⚡ Fullstack | 4 | 15 | 11 | 6 | **36** |
| 🔧 Backend | 1 | 3 | 7 | 7 | **18** |
| **Total** | **6** | **27** | **28** | **17** | **78*** |

> *4 problèmes transverses comptés dans deux agents (ex. contact form = Frontend C1 + Backend C1)

---

## 🎯 Quick wins par agent (top 3 chacun)

### 🎨 Frontend — Top 3
1. **Déplacer `<Navbar>` + `<Footer>` hors du `<main>`** + ajouter skip link (`app/[lang]/layout.tsx`)
2. **Corriger `<button><h3>` + ajouter `aria-expanded`** dans `faqs.tsx`
3. **Ajouter `aria-label`** sur les inputs de recherche (site-search + docs-search)

### ⚡ Fullstack — Top 3
1. **Renommer `proxy.ts` → `middleware.ts`** — une commande, redirects i18n opérationnels
2. **Ajouter `alternates`** dans `generateMetadata` de `blog/page.tsx` et `blog/[category]/page.tsx`
3. **Ajouter `try/catch` par fichier** dans `lib/blog/posts.ts` + créer `error.tsx` dans `/blog/` et `/docs/`

### 🔧 Backend — Top 3
1. **Wirer le formulaire contact** à Resend/Sendgrid (remplacer le `void values`)
2. **Créer `.env.example`** + ajouter `.env*` dans `.gitignore`
3. **Ajouter les headers de sécurité** (`X-Frame-Options`, `CSP`, etc.) dans `next.config.ts`
