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

114 lines
3.2 KiB
TypeScript

import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { getSegmentData, getAllSegmentSlugs } from '@/data/segments';
import { LOCALES, LOCALE_HTML_LANG, LOCALE_OG, DEFAULT_LOCALE, isLocale } from '@/i18n';
import { MYINFOMATE_LINKEDIN, breadcrumbSchema, softwareOffers } from '@/data/structured-data';
import SegmentPageClient from './SegmentPageClient';
const SITE_URL = 'https://myinfomate.be';
export function generateStaticParams() {
const slugs = getAllSegmentSlugs();
return LOCALES.flatMap((lang) =>
slugs.map((segment) => ({ lang, segment }))
);
}
export async function generateMetadata({
params,
}: {
params: Promise<{ lang: string; segment: string }>;
}): Promise<Metadata> {
const { lang, segment } = await params;
if (!isLocale(lang)) return {};
const data = getSegmentData(segment);
if (!data) return {};
const m = data.meta[lang];
const languages = Object.fromEntries(
LOCALES.map((l) => [LOCALE_HTML_LANG[l], `/${l}/${segment}`])
);
return {
title: m.title,
description: m.description,
openGraph: {
title: m.title,
description: m.description,
url: `${SITE_URL}/${lang}/${segment}`,
siteName: 'MyInfoMate',
locale: LOCALE_OG[lang],
alternateLocale: LOCALES.filter((l) => l !== lang).map((l) => LOCALE_OG[l]),
type: 'website',
images: [{ url: '/myinfomate-logo.png' }],
},
twitter: {
card: 'summary_large_image',
title: m.title,
description: m.description,
},
alternates: {
canonical: `/${lang}/${segment}`,
languages: { ...languages, 'x-default': `/${DEFAULT_LOCALE}/${segment}` },
},
};
}
export default async function SegmentPage({
params,
}: {
params: Promise<{ lang: string; segment: string }>;
}) {
const { lang, segment } = await params;
if (!isLocale(lang)) notFound();
const data = getSegmentData(segment);
if (!data) notFound();
const faqItems = data.translations[lang].faq.items;
const faqSchema = {
'@context': 'https://schema.org',
'@type': 'FAQPage',
mainEntity: faqItems.map((item) => ({
'@type': 'Question',
name: item.question,
acceptedAnswer: {
'@type': 'Answer',
text: item.answer,
},
})),
};
const softwareSchema = {
'@context': 'https://schema.org',
'@type': 'SoftwareApplication',
name: 'MyInfoMate',
applicationCategory: 'BusinessApplication',
operatingSystem: 'iOS, Android, Web',
offers: softwareOffers,
sameAs: [MYINFOMATE_LINKEDIN],
description: data.meta[lang].description,
url: `${SITE_URL}/${lang}/${segment}`,
};
const breadcrumb = breadcrumbSchema([
{ name: 'MyInfoMate', path: `/${lang}` },
{ name: data.translations[lang].hero.badge, path: `/${lang}/${segment}` },
]);
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumb) }}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(faqSchema) }}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(softwareSchema) }}
/>
<SegmentPageClient data={data} lang={lang} />
</>
);
}