myinfomate-landing/audit-seo-geo.md
Thomas Fransolet 0dfefc7bb4 SEO/GEO : prérendu statique, 8 nouvelles pages, schémas et vraies captures
Chantier de l'audit audit-seo-geo.md — 20 des 21 actions.

Technique
- Prérendu statique rétabli : le layout racine passe dans [lang], plus de
  headers() ni de x-pathname. 0 page prérendue avant, 89 après.
- Schémas : offres 39/99/179 €, Organization ancrée en Belgique (adresse,
  TVA, areaServed, LinkedIn), FAQPage sur la home, BreadcrumbList partout.
- Hiérarchie h1/h2/h3 remise dans l'ordre ; liens morts corrigés ; liens
  légaux en <Link> — le lint passe de 15 erreurs à 0.
- Les 9 modules de la home sont enfin dans le HTML (8 manquaient).
- sharp en dépendance : mesuré, sans lui /_next/image renvoyait le JPEG
  source (64 Ko), avec lui du WebP (26 Ko).

Contenu
- Nouvelles pages en 4 langues : /tarifs, /assistant-ia, /borne-kiosk,
  /visite-hors-ligne, /marches-publics.
- Blog /fr/ressources (2 articles) et comparatifs sourcés et datés
  myinfomate-vs-smartify et -vs-stqry, en français.
- Pages légales sous /{lang}/ : mentions et confidentialité traduites, CGU
  publiées (le lien de consentement de /signup ne menait nulle part) avec
  un avis traduit, la version française faisant foi.
- Vocabulaire acheteur dans les 6 segments : RGPD, marchés publics,
  accessibilité — en disant l'absence de certification Access-i.
- Captures réelles de la V3 et carte de parcours ; alt textes localisés.
  1,1 Mo d'images économisé.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 18:03:44 +02:00

336 lines
34 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Audit SEO / GEO — myinfomate-landing
> Audit en lecture seule du repo `myinfomate-landing`, réalisé le 2026-09-09.
> Périmètre : code source uniquement. Aucun test réseau sur `https://myinfomate.be` n'a été fait —
> tout ce qui n'est pas dérivable du code est marqué **non vérifiable depuis le code**.
---
## 0. Suivi d'avancement
> Mis à jour le 2026-09-10. Les numéros renvoient au plan d'action du §6.
| # | Action | État |
|---|---|---|
| 1 | Hiérarchie `h2`/`h3` | ✅ 2026-09-10 — libellé décoratif en `<p>`, titre de section en `h2`, cartes en `h3` (home + segments) |
| 2 | Schema `Organization` | ✅ 2026-09-10 — adresse, `vatID`, `taxID`, `areaServed`, `sameAs` LinkedIn Unov ; `url` passé à `https://unov.be` |
| 3 | 9 panneaux `ModulesSection` dans le DOM | ✅ 2026-09-10 — vérifié dans le HTML servi |
| 4 | Offres Essentiel/Pro/Premium + `AggregateOffer` | ✅ 2026-09-10 — `src/data/structured-data.ts`, HTVA (`valueAddedTaxIncluded: false`) |
| 5 | Liens morts `href="#"` | ✅ 2026-09-10 — logo et ACCUEIL → `/{lang}`, globe du footer → unov.be (home **et** segments, le 2ᵉ n'était pas relevé par l'audit) |
| M4 | TVA écrite dans les mentions légales | ✅ 2026-09-10 |
| M7 | Profils sociaux | ✅ 2026-09-10 — vitrine LinkedIn MyInfoMate créée, dans `sameAs` du `SoftwareApplication` ; fiche produit LinkedIn soumise pour évaluation |
| 6 | `llms.txt` FR + prix + `robots.txt` | ✅ 2026-09-10 — `llms.txt` en français (vocabulaire acheteur, grille complète, TVA), anglais déplacé dans `llms-en.txt`, les deux cités dans `robots.txt` |
| 7 | Prérendu statique | ✅ 2026-09-10 — `<html lang>` dans `[lang]/layout.tsx` (layout racine par langue), pages légales dans le groupe `(legal)` avec leur propre layout, plus de `headers()` ni de `x-pathname`. **58 pages prérendues**, servies en `x-nextjs-cache: HIT` |
| 8 | `/{lang}/tarifs` | ✅ 2026-09-10 — grille + 7 FAQ tarifaires ×4 langues, `FAQPage` + offres ; lien « Tarifs » du menu (home et segments) pointe dessus ; grille extraite dans `components/PricingPlans.tsx` (était copiée deux fois) |
| 9 | `/{lang}/assistant-ia` | ✅ 2026-09-10 — **4 langues** (736 à 846 mots), 6 FAQ + `FAQPage` par langue, `hreflang` complet, liée depuis la section IA de la home et dans le sitemap. Section « Ce qu'il ne fait pas » assumée : dire les limites plutôt que survendre l'IA |
| — | Liens légaux du footer | ✅ 2026-09-10 — passés en `<Link>` (home, segments, signup) : **le lint du repo est à 0 erreur**, il en comptait 15 |
| — | Page CGU | ✅ 2026-09-10 — `/conditions-generales` publiée (~4 000 mots) : la case de consentement de `/signup` faisait accepter des CGU **introuvables**. Liée depuis les pieds de page et le sitemap. ⚠️ validation juridique toujours due |
| M4 | Frais de mise en place | ✅ 2026-09-10 — **décision de Thomas : pas de montant affiché.** À la place, dans les 4 langues : « frais de mise en place **uniques, chiffrés dans le devis** », et la FAQ de `/tarifs` dit ce qu'ils couvrent et qu'ils précèdent la signature (voir carte kanban 180) |
| 12 | Vocabulaire acheteur dans les 6 segments | 🟠 2026-09-10 — **français fait**. Nouvelles FAQ par segment : RGPD (les 6), marchés publics (5 — pas hôtels, secteur privé), accessibilité/Access-i (3). « Médiation numérique », « parcours de visite » et « cartel numérique » injectés dans les intros. **NL/EN/DE pas encore traduits** : les FAQ françaises sont plus fournies que les autres langues |
| 14 | Page marchés publics | 🟠 partiellement couvert par la FAQ des segments ; page dédiée toujours à faire |
| 10 | FAQ sur la homepage | ✅ 2026-09-10 — 8 questions ×4 langues + `FAQPage` (produit, prix, canaux, hors ligne, marque blanche, délai, hébergement des données). C'était la page qu'un LLM atteint en premier, et elle n'avait aucune FAQ |
| 13 | `BreadcrumbList` | ✅ 2026-09-10 — sur segments, cas clients (liste et détail), tarifs et assistant IA ; helper `breadcrumbSchema()` dans `structured-data.ts` |
| 21 | Dates du sitemap | ✅ 2026-09-10 — `lastModified: new Date()` (date du build sur les 55 URLs) remplacé par des dates réelles par famille de pages, constante `LAST_MODIFIED` en tête de `sitemap.ts` **à mettre à jour avec le contenu** |
| 12 bis | Traduction des FAQ segments | ✅ 2026-09-10 — **42 items** ajoutés en EN/NL/DE. Parité atteinte : 10 / 10 / 8 / 7 / 8 / 9 FAQ par segment dans les 4 langues |
| 16 | Images | ✅ 2026-09-10 — recompressées (palette PNG, `effort: 10`) : `app-screenshot-1` 731 → **232 Ko**, `app-screenshot-2` 547 → **185 Ko**, `myinfomate-logo` 240 → **57 Ko**. Qualité vérifiée à l'œil. `map_generated` déjà optimale. **1,1 Mo économisé.** `sharp` a servi en installation temporaire, il n'est **pas** ajouté aux dépendances |
| — | `google-maps-route.png` (699 Ko) | ⚠️ **référencé nulle part dans `src/`** — supprimable, mais je n'y touche pas sans ton accord |
| — | Captures d'écran de l'app | ✅ 2026-09-10 — remplacées par les **vraies captures de la V3** fournies par Thomas (app du Fort de Saint-Héribert) : accueil sombre en grille bento et fiche article dans les deux téléphones du hero, liste des sections dans le mockup « white label », où un cadre vide affichait une icône de palette. Anciennes captures `.png` supprimées, nouvelles en JPEG (129 / 121 / 107 Ko). **Textes alternatifs réécrits et localisés** dans les 4 langues — ils étaient en anglais et décrivaient l'ancienne interface |
| 14 | `/{lang}/marches-publics` | ✅ 2026-09-10 — 4 langues, 5 FAQ. Argument central : **Visit Namur, attribué sur cahier des charges publié**. Dit franchement l'absence de certification Access-i et d'audit, et renvoie au service marchés pour les seuils plutôt que de citer un chiffre non vérifié |
| 17 | `/{lang}/borne-kiosk` et `/{lang}/visite-hors-ligne` | ✅ 2026-09-10 — 4 langues, 4 FAQ chacune, adossées à Visit Namur et au Fort de Saint-Héribert. Gabarit partagé `components/ContentPage.tsx` |
| — | Optimisation d'images en prod | ✅ 2026-09-10 — **`sharp` ajouté aux dépendances, après mesure** : sans lui, `/_next/image` renvoyait le JPEG source (64 Ko) ; avec lui, du **WebP de 26 Ko**. ⚠️ à confirmer au premier build Docker (binaires `linuxmusl`) |
| — | Maillage interne vers les 5 pages produit | ✅ 2026-09-10 — colonne « Solutions » dans le pied de page de la **home et des pages segment** (3 colonnes vides récupérées), plus des liens **dans le contenu**, qui valent davantage : carte « Mode Kiosk » → `/borne-kiosk`, point « Contenu hors ligne » → `/visite-hors-ligne`, bandeau acheteur public sous la grille de `/tarifs``/marches-publics`. Compté sur les pages servies : chaque page produit reçoit 1 à 3 liens par page. **Sitemap : 67 URLs** |
| 15 | Pages légales en 4 langues | ✅ 2026-09-10 — mentions légales et confidentialité **traduites**, CGU laissées en français avec un **avis traduit** (« la version française fait foi ») et le corps marqué `lang="fr"` : traduire un contrat qu'un juriste va corriger, c'est du travail à refaire. Les 3 pages vivent sous `/{lang}/…`, les anciennes URLs redirigent vers la langue du visiteur |
| 18 | Pages comparatives | ✅ 2026-09-10 — `/fr/comparatif/myinfomate-vs-smartify` et `…-vs-stqry`, **prix relevés à la source le 10/09** (Smartify : 1 800 / 3 500 / 9 500 £ par an ; STQRY : 199 $/mois, +100 $ Pro, 2 000 $ de frais uniques d'app native), sources liées et datées. Chaque page dit **ce que le concurrent fait mieux**. ⚠️ Guideius et izi.TRAVEL écartés : le premier ne publie aucun prix, le second a un modèle gratuit non comparable |
| 20 | Blog `/fr/ressources` | ✅ 2026-09-10 — index + 2 articles (audioguide vs application, borne vs BYOD), schema `BlogPosting`. **Français seulement** ; les autres langues répondent 404, c'est volontaire |
| 11 | Témoignages clients | ⏸️ **écarté le 2026-09-10 par Thomas** — il ne souhaite pas solliciter les clients pour l'instant, même satisfaits. Le type `CasClient` reste sans champ témoignage : à rouvrir le jour où un client en propose un spontanément |
| 19 | Pages XR | ⬜ **deux pages à prévoir, pas une** : `/lunettes-connectees` et `/casque-vr`. À écrire quand chaque module sera démontrable — cf. `DOCS/v2/vr-quest-unity-plan.md` et `DOCS/rayban-meta-integration.md` |
Tout est **stagé, pas encore committé** au 2026-09-10 (en attente de validation).
Point connu : une URL inexistante (`/fr/xyz`) répond bien **404 + `noindex`**, mais son HTML initial est le shell d'erreur de Next ; la page 404 (`[lang]/not-found.tsx`) s'affiche après hydratation. Effet de l'absence de layout racine commun ; le corriger demanderait `experimental.globalNotFound`. Sans impact SEO.
Posts LinkedIn de lancement : `DOCS/linkedin-posts-lancement.md`.
---
## 1. Verdict en 5 lignes
1. **Oui, partiellement citable.** Le contenu textuel principal est rendu côté serveur (composants serveur + client components pré-rendus par Next), les prix sont en texte brut, `llms.txt` existe et est bon, et aucun crawler IA n'est bloqué. Un LLM interrogé sur « audioguide pour musée en Belgique » a de la matière.
2. **Mais le site n'est trouvable que sur des requêtes produit, pas sur les requêtes d'acheteur.** Les 6 pages segment sont bien construites (~1 130 mots FR + 6 FAQ chacune), mais il n'existe **aucune** page `/tarifs`, `/assistant-ia`, `/borne-kiosk`, `/visite-offline`, ni page lunettes connectées : le différenciateur IA n'a pas d'URL propre.
3. **Le vocabulaire de l'acheteur public belge est absent** : zéro occurrence de « médiation numérique », « scénographie », « cartel numérique », « accessibilité », « Access-i », « marché public », « RGPD » dans les ~5 700 lignes de contenu de `src/data/`.
4. **Deux fuites de contenu réelles** : 8 des 9 descriptions de modules de la homepage ne sont pas dans le HTML (`ModulesSection.tsx:135`), et aucune page n'est pré-rendue statiquement (`layout.tsx:36` force le SSR sur tout l'arbre).
5. **Les preuves clients sont là mais désincarnées** : 3 cas clients datés et nommés, mais aucun logo, aucun témoignage attribué, aucun lien sortant vers un site client ou une fiche store — c'est ce qui manque le plus à un dossier de marché public.
---
## 2. Inventaire factuel
### Framework et mode de rendu
| Élément | Valeur | Source |
|---|---|---|
| Framework | Next.js **16.1.6**, React 19.2.3, App Router | `package.json:12-13` |
| CSS | Tailwind v4 (`@tailwindcss/postcss`) | `package.json:22` |
| Build | `output: "standalone"`, React Compiler activé | `next.config.ts:5-6` |
| Déploiement | Docker `node:22-alpine`, `node server.js` derrière Traefik | `Dockerfile:8-18`, `README.md` |
| Routing i18n | Middleware Next 16 (`src/proxy.ts`) → redirect vers `/{locale}` selon `Accept-Language` | `src/proxy.ts:28-32` |
| Locales | `fr`, `en`, `nl`, `de``fr` par défaut, html lang `fr-BE` / `nl-BE` | `src/i18n.ts:1-17` |
| **Mode de rendu effectif** | **SSR dynamique sur 100 % des pages de contenu — 0 page prérendue** | voir ci-dessous |
**Le contenu texte est bien dans le HTML servi** — ce n'est pas une SPA client-only, donc le point critique redouté n'est pas présent. Deux composants seulement sont des client components porteurs de contenu (`SegmentPageClient.tsx:1`, `ModulesSection.tsx:1`) ; Next les rend quand même dans le HTML initial, donc leur texte est extractible **à l'exception de ce qui est conditionné par un `useState`** (voir bloquant B2).
**En revanche, la génération statique ne fonctionne pas.** Le `prerender-manifest.json` du dernier build présent dans `.next/` (daté du 13/08) ne contient que trois routes prérendues :
```
PRERENDERED: ['/_global-error', '/icon.png', '/sitemap.xml']
DYNAMIC: []
```
Cause : `src/app/layout.tsx:36-37` appelle `await headers()` pour lire `x-pathname`. Cet accès aux headers de requête opte **tout l'arbre de routes** hors du rendu statique. Résultat : les `generateStaticParams()` déclarés dans `[lang]/page.tsx:27`, `[lang]/[segment]/page.tsx:9` et `[lang]/signup/page.tsx:25` ne produisent aucun HTML statique — chaque visite, y compris chaque crawl, déclenche un rendu React complet.
### Routes trouvées
| Route | Fichier | Titre (FR) | H1 | Contenu |
|---|---|---|---|---|
| `/{lang}` | `src/app/[lang]/page.tsx` | *MyInfoMate — Application guide visiteur pour musées & lieux* | `MyInfoMate - La solution facile pour créer, gérer et diffuser du contenu interactif` (`HomeClient.tsx:29-31`) | ~1 990 mots FR |
| `/{lang}/musees` | `[segment]/page.tsx` | *Application Guide de Visite pour Musées \| MyInfoMate* | `Votre musée mérite une application à la hauteur de vos collections` | ~1 134 mots FR, 6 FAQ |
| `/{lang}/offices-tourisme` | idem | même structure, meta propre | H1 hero segment | ~1 100 mots, 6 FAQ |
| `/{lang}/parcs-naturels` | idem | | | ~1 100 mots, 6 FAQ |
| `/{lang}/hotels-loisirs` | idem | | | ~1 100 mots, 6 FAQ |
| `/{lang}/evenements` | idem | | | ~1 100 mots, 6 FAQ |
| `/{lang}/education-culture` | idem | | | ~1 100 mots, 6 FAQ |
| `/{lang}/cas-clients` | `cas-clients/page.tsx` | listing | oui | court |
| `/{lang}/cas-clients/musee-de-la-fraise` | `cas-clients/[slug]/page.tsx` | *Musée de la Fraise — Premier client MyInfoMate \| Kiosk & App Mobile* | `De la vitrine au numérique : la naissance de MyInfoMate` (`:101`) | ~400 mots |
| `/{lang}/cas-clients/fort-de-saint-heribert` | idem | | | ~400 mots |
| `/{lang}/cas-clients/visit-namur` | idem | | | ~400 mots |
| `/{lang}/signup` | `signup/page.tsx` | *Créer mon compte — Plan Essentiel* | — | **`robots: {index:false}`** (`:41`) — correct |
| `/mentions-legales` | `app/mentions-legales/page.tsx` | *Mentions légales* | `Mentions légales` (`:18`) | FR uniquement, hors `[lang]` |
| `/confidentialite` | `app/confidentialite/page.tsx` | *Politique de confidentialité* | `Politique de confidentialité` (`:18`) | FR uniquement, hors `[lang]` |
**Total indexable : 11 URLs × 4 langues + 2 pages légales = 46 URLs.** Cohérent avec `src/app/sitemap.ts`, qui génère exactement ces entrées avec `alternates.languages` par entrée (`sitemap.ts:8-15`).
### Fondations techniques — état
| Point | État | Détail |
|---|---|---|
| `robots.txt` | ✅ présent | `public/robots.txt``User-agent: *` / `Allow: /` / `Disallow: /api/` |
| Blocage crawlers IA | ✅ **aucun bloqué** | GPTBot, ClaudeBot, anthropic-ai, PerplexityBot, Google-Extended, CCBot, Bingbot sont **tous autorisés** par le `User-agent: *`. Aucune règle nominative, ni Allow ni Disallow (`robots.txt:1-3`) |
| `sitemap.xml` | ✅ généré + référencé | `src/app/sitemap.ts`, référencé `robots.txt:5` |
| `llms.txt` | ✅ présent et de bonne qualité | `public/llms.txt`, 85 lignes — produit, features, modes de déploiement, assistant IA, clients, pricing, société |
| Canonical | ✅ sur toutes les pages | `[lang]/page.tsx:60`, `[segment]/page.tsx:48`, `cas-clients/[slug]`, légales `:7` |
| hreflang | ✅ complet + `x-default` | `layout.tsx:68-71`, `[lang]/page.tsx:59-62`, et aussi dans `sitemap.ts:13` |
| Open Graph | ✅ | `layout.tsx:53-62` + per-page ; **OG image dynamique** `[lang]/opengraph-image.tsx` (edge runtime, 1200×630, locale-aware) et variante par segment |
| Twitter Card | ✅ `summary_large_image` | `layout.tsx:63-67` |
| Données structurées | ✅ **JSON-LD** (pas microdata) | Organization + WebSite + SoftwareApplication (home) ; FAQPage + SoftwareApplication (segments) ; Article (cas clients) |
| BreadcrumbList | ❌ absent | aucune occurrence dans le repo |
| Images | ⚠️ | `next/image` partout (donc WebP/AVIF à la volée), `priority` sur le hero (`HomeClient.tsx:53`), `alt` renseignés. Mais les sources sont des PNG lourds : `app-screenshot-1.png` 748 Ko, `google-maps-route.png` 715 Ko, `app-screenshot-2.png` 560 Ko, `myinfomate-logo.png` 246 Ko |
| Temps de chargement réel | **non vérifiable depuis le code** — le Lighthouse est d'ailleurs encore coché « à faire » dans `TODO-SEO.md:39` |
---
## 3. Bloquants critiques
### B1 — Aucune page n'est prérendue : tout est SSR à chaque requête
**Fichier : `src/app/layout.tsx:36-37`**
```ts
const h = await headers();
const pathname = h.get("x-pathname") ?? "/";
```
Cet appel dans le layout racine force le rendu dynamique de l'intégralité de l'application. `prerender-manifest.json` le confirme : trois routes prérendues, aucune page de contenu. Les `generateStaticParams()` déclarés dans trois fichiers sont inertes.
*Conséquence* : pas de blocage d'indexation (le HTML est bien complet dans la réponse), mais un TTFB dépendant du serveur à chaque crawl, et la perte de toute mise en cache statique/CDN. Sur un VPS unique derrière Traefik (`README.md`), c'est le premier facteur de lenteur.
*Correctif* : le layout racine n'a besoin de la locale que pour `<html lang>` et des métadonnées de repli — or elle est déjà disponible via le segment `[lang]`. Déplacer `resolveLocale()` + `generateMetadata` vers un `src/app/[lang]/layout.tsx` (à créer) recevant `params.lang`, et laisser le layout racine muet. `[lang]/page.tsx:31` et `[segment]/page.tsx:16` définissent déjà leurs métadonnées complètes, donc le layout racine est en grande partie redondant.
### B2 — 8 des 9 descriptions de modules de la homepage sont absentes du HTML
**Fichier : `src/components/ModulesSection.tsx:20, 73, 125, 135`**
```tsx
const [activeModule, setActiveModule] = useState(0);
...
<h3 ...>{modules[activeModule].title}</h3>
<p ...>{modules[activeModule].description}</p>
```
Seul le module d'index 0 (Carte interactive) voit sa `description` et sa `value` rendues. Les huit autres — Quiz/jeux, Vidéo, Article, Agenda, **Offline**, Web, Statistiques, **Escape game** — n'existent dans le HTML que sous forme de libellé de bouton (`ModulesSection.tsx:59`). Or « mode offline » et « escape game » sont deux des arguments les plus différenciants : ils figurent dans `llms.txt:26,29` mais pas dans le HTML de la homepage.
*Correctif* : rendre les neuf panneaux et masquer les inactifs en CSS (`hidden` / `aria-hidden`), exactement comme le fait déjà correctement la FAQ des pages segment (`SegmentPageClient.tsx:652` — collapse par `max-h-0`, contenu toujours présent dans le DOM).
### B3 — Aucun schema `Offer` pour les plans Pro / Premium / Enterprise
**Fichiers : `src/app/[lang]/page.tsx:100-110` et `[segment]/page.tsx:84-94`**
Le `SoftwareApplication` ne déclare qu'une seule offre, à 39 €. Les prix 99 € et 179 € sont bien en texte brut dans le HTML (`SegmentPageClient.tsx:460` et suivantes) — donc lisibles par un LLM — mais invisibles pour Google Rich Results et pour tout agent qui s'appuie sur les données structurées. À la question « combien coûte MyInfoMate pour avoir l'assistant IA ? », un LLM devra deviner.
*Correctif* : remplacer `offers` par un tableau de trois à quatre `Offer` (`name: "Essentiel" / "Pro" / "Premium"`, `price`, `priceCurrency: "EUR"`, périodicité), plus un `AggregateOffer` avec `lowPrice: 39` / `highPrice: 179`.
---
## 4. Manques importants (existe mais mal fait)
### M1 — Hiérarchie de titres inversée sur toutes les pages
Le label décoratif en petites capitales est un `<h2>`, et le vrai titre de section un `<h3>`. Vérifiable partout :
- `HomeClient.tsx:78-79``<h2>{t.strategic.sectionLabel}</h2>` puis `<h3>{t.strategic.sectionTitle}</h3>`
- `HomeClient.tsx:126-127`, `:181-182`, `:301-302`, `:421-422`, `:474-475`
- `SegmentPageClient.tsx:310-311`, `:333-334`, `:363-364`, `:398-399`, `:636-637`
- `ModulesSection.tsx:34-35`
Un extracteur de plan de page lit donc « LE DÉFI DES MUSÉES » comme titre de niveau 2 et « Ce que vivent la plupart des musées » comme sous-titre : l'inverse exact. Correctif purement cosmétique — inverser les balises, garder les classes.
### M2 — Le schema `Organization` n'ancre pas l'entreprise en Belgique
**`src/app/[lang]/page.tsx:70-81`** ne contient que `name`, `url`, `logo`, `contactPoint`. Manquent `address` (PostalAddress : Rue René Copette 3, 5020 Namur, BE), `areaServed` (BE / FR / LU), `vatID` ou `taxID` (BCE 0776.895.467), et `sameAs` (aucun profil externe déclaré). Toutes ces données existent pourtant en clair dans `mentions-legales/page.tsx:24-28`. C'est le signal le plus direct qu'un LLM utilise pour répondre à « une solution **belge** ».
### M3 — Les mentions légales sont hors du système i18n
`src/app/mentions-legales/page.tsx` et `src/app/confidentialite/page.tsx` vivent hors de `[lang]`, sont explicitement exclues du middleware (`proxy.ts:5`), n'existent qu'en français et n'ont pas de `hreflang`. Un acheteur néerlandophone ou luxembourgeois qui vérifie la conformité de l'éditeur tombe sur du français. Le sitemap ne les référence qu'en une seule version (`sitemap.ts:66-77`).
### M4 — TVA jamais écrite, RGPD jamais mis en avant côté commercial
`mentions-legales/page.tsx:25` donne « Numéro d'entreprise BCE : 0776.895.467 » mais jamais le format TVA `BE 0776.895.467` — c'est pourtant la chaîne que cherche le service comptable d'une commune. Par ailleurs « RGPD » n'apparaît **que** dans `confidentialite/page.tsx` : `grep -ric "RGPD" src/data/*.ts` retourne 0. Le seul argument d'hébergement européen est une ligne de la page signup (`translations.ts:207`, « Hébergé en Europe ») — sur une page en `noindex`.
### M5 — Cas clients : aucune preuve vérifiable de l'extérieur
`src/data/cas-clients.ts` donne pour chaque client `client`, `year`, `location` (`:49-52`, `:223-225`, `:397-399`), mais l'interface `CasClient` (`:36-42`) ne prévoit **ni URL du site client, ni lien vers une fiche store, ni témoignage, ni logo**.
- Aucun témoignage nommé : zéro occurrence de `quote` / `testimonial` / `author` dans le fichier.
- Aucun logo client : `public/` ne contient que le logo MyInfoMate et quatre screenshots génériques.
- Aucun lien sortant : pas un seul `href="http"` dans `cas-clients/[slug]/page.tsx`.
- Les « résultats » sont qualitatifs — `{value:'2021', label:'Premier déploiement'}`, `{value:'100%', label:'Autonomie éditoriale'}` (`cas-clients.ts:97-100`) : aucun chiffre de fréquentation, de nombre de POI, de langues ou de sessions.
- Le schema `Article` (`cas-clients/[slug]/page.tsx:56-65`) n'a ni `image`, ni `dateModified`, ni `about`/`mentions` pointant vers le lieu.
Les chiffres concrets présents ailleurs restent maigres : `llms.txt:64-68` mentionne iBeacon, offline 100 %, iOS + Android — c'est le contenu le plus factuel du site, et il n'est pas dans le HTML.
### M6 — Aucune date de mise à jour visible
`sitemap.ts:18` fixe `lastModified: new Date()`, c'est-à-dire la date du build, appliquée uniformément à toutes les URLs : ce n'est pas un signal de fraîcheur, c'est du bruit. Aucune page n'affiche de date dans son HTML ; les cas clients affichent l'année du projet (`cas-clients/[slug]/page.tsx:99`), pas la date de rédaction.
### M7 — Deux liens morts et aucun profil social
- `HomeClient.tsx:766` : `<a href="#">` avec une icône « public » dans le footer — bouton social qui ne pointe nulle part.
- `NavBar.tsx:59` et `:149` : logo et « ACCUEIL » en `href="#"` (scroll JS) au lieu de `href={/${lang}}`. Un crawler n'a pas de lien de retour vers la home depuis le header.
- Aucun `sameAs` LinkedIn — la création de la page LinkedIn Unov est d'ailleurs encore ouverte dans `TODO-SEO.md:40`.
### M8 — Images sources non optimisées
748 Ko, 715 Ko et 560 Ko en PNG pour des screenshots d'app. `next/image` réencode, mais l'optimiseur travaille sur ces sources à chaque cache miss. `myinfomate-logo.png` (246 Ko) sert à la fois de favicon (`layout.tsx:52`), d'image OG de repli (`layout.tsx:61`) et d'image 32 px dans le footer (`HomeClient.tsx:755-760`). Le paquet `sharp` n'est pas dans `package.json` ; le comportement réel de l'optimiseur dans le conteneur `node:22-alpine` est **non vérifiable depuis le code**.
---
## 5. Opportunités (n'existe pas et devrait exister)
### O1 — Pages par intention manquantes
Aucune de ces routes n'existe dans `src/app/` :
| URL attendue | État | Commentaire |
|---|---|---|
| `/tarifs` | ❌ | Les prix ne vivent que dans des sections `#pricing` de la home et des six pages segment. Une requête « prix audioguide musée » n'a aucune page à faire remonter. Le contenu existe déjà (`SegmentPageClient.tsx:395-628`) — il suffit de l'extraire dans une route. |
| `/assistant-ia` | ❌ | **Le différenciateur n'a pas de page.** Il est une section de la home (`HomeClient.tsx:170-260`) et un paragraphe de `llms.txt:42-49`. Face à Guideius ou izi.TRAVEL, c'est l'argument qui n'existe nulle part ailleurs — et il n'est pas adressable. |
| `/borne-kiosk` | ❌ | Le kiosk n'est qu'un mode de déploiement dans `#deployment-modes` (`translations.ts:53`) ; le cas Visit Namur porte entièrement dessus. Mérite une page. |
| `/visite-offline-sans-reseau` | ❌ | Le mode offline est un module (`ModulesSection.tsx:12`) dont la description **n'est même pas dans le HTML** (bloquant B2). Le cas Fort de Saint-Héribert est un argument offline parfait (iBeacon, aucune couverture réseau). |
| `/musee-plein-air` | ❌ | Partiellement couvert par `/parcs-naturels`. |
| `/lunettes-connectees` | ❌ | Une seule occurrence dans tout le contenu : `translations.ts:60`, « Meta Quest et lunettes connectées », dans un bloc « à venir ». Pas de page, pas de schema. |
| `/application-office-de-tourisme` | ✅ équivalent | `/{lang}/offices-tourisme` existe et est complet. |
### O2 — Vocabulaire de l'acheteur culturel : absent
`grep -ric` sur `src/data/translations.ts`, `segments.ts`, `cas-clients.ts` :
| Terme | Occurrences |
|---|---|
| « médiation numérique » | **0** |
| « scénographie » | **0** |
| « cartel numérique » | **0** |
| « accessibilité » | **0** |
| « Access-i » | **0** |
| « marché public » | **0** |
| « RGPD » | **0** (hors page confidentialité) |
| « réalité augmentée » | **0** |
| « BYOD » | 8 (bon) |
| « audioguide » / « audio guide » | 24 / 30 (bon) |
| « Namur » | 34 (bon, surtout via les cas clients) |
| « Wallonie » | 2 |
| « Belgique » | 3 (uniquement dans `cas-clients.ts`) |
Le site parle la langue du produit, pas celle du conservateur ni celle du service marchés publics. « Médiation numérique » est le terme employé par les appels à projets de la Fédération Wallonie-Bruxelles : ne pas l'avoir écrit une seule fois est une occasion manquée gratuite.
### O3 — Aucune page marchés publics
Rien dans le repo sur les seuils, la procédure de marché de faible montant, la capacité à répondre à un cahier des charges, ni de document type. C'est pourtant l'objection numéro un d'une commune ou d'une province.
### O4 — Aucune page comparative
`src/app/` ne contient aucune route `comparatif`. Le comparatif existe **à l'intérieur** des pages segment (`Segment.comparison`, `segments.ts:23-26`) mais compare des catégories abstraites (« App visiteur générique », « Fiches pédagogiques papier »). Les seuls concurrents nommés le sont dans les meta descriptions (« Alternative à Smartify », `segments.ts:81`) et dans un pain point (`segments.ts:~120` : « Smartify, SmartGuide ou STQRY coûtent entre €100 et €925/mois »). **Aucune mention de Guideius, izi.TRAVEL, MySmartJourney, Musa Guide, Cirkwi ou Apptree** dans tout le repo — alors que ce sont les noms réellement tapés par un acheteur belge ou français.
Ce n'est pas une erreur, c'est le levier GEO le plus rentable qui reste : les pages « alternative à X » sont exactement ce qu'un LLM ingère pour répondre à une question de comparaison. `TODO-SEO.md:56-59` les liste déjà comme à faire.
### O5 — Aucun blog
`src/app/` ne contient aucune route `blog`, `ressources` ou `articles`. `TODO-SEO.md:49-54` a déjà les cinq sujets d'articles identifiés ; aucun n'est écrit. Le seul contenu éditorial du site est du contenu produit — pas un texte ne répond à une question que se pose un responsable de musée avant d'avoir entendu parler de MyInfoMate.
### O6 — `llms.txt` : bon fichier, trois angles morts
`public/llms.txt` est de bonne facture (85 lignes, structuré, avec liens vers les trois cas clients). Ses faiblesses :
- **Il est uniquement en anglais** (`llms.txt:3`) alors que le marché cible est francophone. Un LLM interrogé en français citera plus volontiers une source française.
- **Il n'est pas référencé dans `robots.txt`**, ni lié depuis le HTML.
- Les prix y sont incomplets (`llms.txt:74`, « Starting from €39/month ») alors que la grille 39 / 99 / 179 est publique sur le site.
### O7 — Pas de FAQ sur la homepage
Les six pages segment ont chacune six questions et un `FAQPage` JSON-LD (`[segment]/page.tsx:65-76`) — c'est très bien fait. La homepage n'a **aucune** FAQ ni `FAQPage`. C'est pourtant la page qu'un LLM atteint en premier.
---
## 6. Plan d'action priorisé
Trié par ratio impact / effort. **T** = correctif technique, **C** = écriture de contenu.
| # | Action | Type | Impact | Effort |
|---|---|---|---|---|
| 1 | Inverser `h2`/`h3` sur les 12 sections concernées (`HomeClient.tsx:78,126,181,301,421,474` ; `SegmentPageClient.tsx:310,333,363,398,636` ; `ModulesSection.tsx:34`) | T | Moyen | **0,5 h** |
| 2 | Enrichir le schema `Organization` : `address`, `areaServed: ["BE","FR","LU"]`, `vatID: "BE0776895467"`, `sameAs` (`[lang]/page.tsx:70-81`) | T | **Fort** | **0,5 h** |
| 3 | Rendre les 9 panneaux de `ModulesSection` dans le DOM, masquer en CSS au lieu de `modules[activeModule]` (`ModulesSection.tsx:73,125`) — récupère « offline » et « escape game » dans le HTML | T | **Fort** | **1 h** |
| 4 | Passer `SoftwareApplication.offers` à un tableau de 3 `Offer` + `AggregateOffer` 39179 € (`[lang]/page.tsx:100`, `[segment]/page.tsx:84`) | T | **Fort** | **1 h** |
| 5 | Corriger les liens morts : footer `href="#"` (`HomeClient.tsx:766`), logo et ACCUEIL de la NavBar (`NavBar.tsx:59,149`) → `/{lang}` | T | Moyen | **0,5 h** |
| 6 | Traduire `llms.txt` en français (ou publier FR + `llms-en.txt`), y ajouter la grille 39/99/179 et le référencer depuis `robots.txt` | C | **Fort** | **1,5 h** |
| 7 | Déplacer `resolveLocale()`/`generateMetadata` de `layout.tsx:36` vers un `[lang]/layout.tsx` → réactive le prérendu statique des 46 URLs | T | **Fort** | **23 h** |
| 8 | Créer `/{lang}/tarifs` en réutilisant la section pricing existante + une FAQ tarifaire | T+C | **Fort** | **3 h** |
| 9 | Créer `/{lang}/assistant-ia` — le différenciateur mérite son URL, avec FAQ et schema | C | **Fort** | **4 h** |
| 10 | Ajouter un `FAQPage` de 68 questions en bas de homepage (réutiliser le format de `segments.ts`) | C | Moyen | **3 h** |
| 11 | Ajouter au type `CasClient` : `clientUrl`, `storeUrls`, `testimonial {quote, name, role}` — et les remplir après accord des 3 clients | T+C | **Fort** | **4 h** + relance clients |
| 12 | Injecter le vocabulaire acheteur (« médiation numérique », « accessibilité », « scénographie », « parcours de visite », « RGPD », « hébergement européen ») dans `translations.ts` et les 6 segments FR | C | **Fort** | **46 h** |
| 13 | Ajouter `BreadcrumbList` sur les pages segment et cas clients | T | Faible | **1 h** |
| 14 | Créer `/{lang}/marches-publics` : seuils, marché de faible montant, capacité à répondre, coordonnées BCE/TVA | C | **Fort** (public belge) | **4 h** |
| 15 | Traduire `/mentions-legales` et `/confidentialite` en 4 langues et les faire passer sous `[lang]` (ajuster `proxy.ts:5`) | T+C | Moyen | **3 h** |
| 16 | Recompresser les 4 PNG de `public/` (748 / 715 / 560 / 246 Ko → cible < 150 Ko) et produire un logo 32 px distinct du logo OG | T | Moyen | **1 h** |
| 17 | Créer `/{lang}/borne-kiosk` et `/{lang}/visite-offline-sans-reseau`, adossées aux cas Visit Namur et Fort de Saint-Héribert | C | Moyen | **6 h** |
| 18 | Trois pages comparatives : `myinfomate-vs-izi-travel`, `myinfomate-vs-guideius`, `myinfomate-vs-smartify` factuelles, sourcées, sans dénigrement | C | **Fort** (GEO) | **8 h** |
| 19 | Créer `/{lang}/lunettes-connectees` (à faire quand le module sera démontrable cf. `DOCS/rayban-meta-integration.md`) | C | Faible aujourd'hui | **4 h** |
| 20 | Ouvrir un blog `/{lang}/ressources` avec les 5 articles déjà listés dans `TODO-SEO.md:49-54` | C | **Fort** à 6 mois | **20 h+** |
| 21 | Remplacer `lastModified: new Date()` par une date réelle par contenu (`sitemap.ts:18`) et afficher une date de mise à jour sur les cas clients | T | Faible | **2 h** |
**Séquence recommandée.** Les items 1 à 6 tiennent en une demi-journée et corrigent l'essentiel des pertes gratuites. L'item 7 est le seul chantier technique de fond. Les items 8, 9, 12 et 14 sont ceux qui changent réellement la réponse d'un LLM à « quelle solution d'audioguide pour mon musée en Belgique ? ».
---
## Annexe — ce qui est déjà bien fait
À ne pas casser :
- Le contenu textuel est intégralement dans le HTML servi ce n'est pas une SPA client-only.
- Aucun crawler IA n'est bloqué (`robots.txt:1-2`).
- `llms.txt` existe et est structuré la majorité des concurrents n'en ont pas.
- Les six pages segment sont de vraies pages d'intention : ~1 130 mots FR, pain points, features, comparatif, six FAQ, le tout en quatre langues, avec `FAQPage` JSON-LD.
- Les FAQ des pages segment sont collapsées en CSS (`max-h-0`, `SegmentPageClient.tsx:652`) et non conditionnées par un `useState` : le texte est bien dans le DOM. C'est le bon pattern exactement ce que `ModulesSection` ne fait pas.
- `hreflang` complet avec `x-default`, sur chaque page **et** dans le sitemap.
- `/signup` est correctement en `noindex, follow` (`signup/page.tsx:41`).
- OG image générée dynamiquement et localisée, avec une variante par segment.
- Les prix (39 / 99 / 179 €) sont en texte brut dans le HTML, pas en image.