diff --git a/TODO-SEO.md b/TODO-SEO.md index fa3eb6d..c0e03b0 100644 --- a/TODO-SEO.md +++ b/TODO-SEO.md @@ -1,102 +1,62 @@ # TODO SEO — myinfomate-landing -Liste des améliorations SEO à réaliser, classées par impact décroissant. +--- + +## Ce qui est fait ✅ + +| # | Item | +|---|------| +| 1 | Routing multilingue + hreflang (fr/en/nl/de, sitemap, `x-default`) | +| 2 | Schema.org FAQPage + SoftwareApplication sur les pages segment | +| 3 | Schema.org Organization + WebSite + SoftwareApplication sur la home | +| 4 | `llms.txt` — résumé produit pour les crawlers LLM (ChatGPT, Perplexity, Claude...) | +| 5 | OG image 1200×630 générée dynamiquement via `opengraph-image.tsx`, locale-aware | +| 6 | Titles + descriptions réécrits avec mots-clés ciblés (< 60 chars, ~155 chars, 4 langues) | +| 7 | Alt texts descriptifs sur toutes les images hero | +| 8 | H1 unique par page, `priority` sur l'image hero, font en `display=swap` | +| 9 | robots.txt + sitemap.xml | --- -## 1. Multilingue avec hreflang (impact: ÉLEVÉ) ✅ +## À faire — code (peut être fait avec Claude) -Le site a 4 langues (FR/EN/NL/DE) mais tout est servi sur la même URL avec `lang="fr"` figé. Google n'indexe qu'une seule version → on rate les recherches NL et DE. +### Moyen terme -- [x] Mettre en place un routing par langue : `/fr`, `/en`, `/nl`, `/de` (ou middleware Next) -- [x] Ajouter `metadata.alternates.languages` dans `layout.tsx` et `[segment]/page.tsx` -- [x] Rendre `` dynamique selon la route -- [x] Adapter le `sitemap.ts` pour générer les URLs de chaque langue -- [x] Vérifier que les balises `` pointent vers la version par défaut +- [x] **Refactor HomeClient en Server Component** — extrait en 4 composants client isolés (`NavBar`, `ModulesSection`, `ContactForm`, `Reveal`). Le reste de la page est rendu côté serveur. CTA buttons convertis en `` + CSS `scroll-behavior: smooth`. + +- [x] **OG image par segment** — `src/app/[lang]/[segment]/opengraph-image.tsx` avec badge segment locale-aware (ex: "Musées & Patrimoine" / "Museums & Heritage") --- -## 2. Page d'accueil en Server Component (impact: MOYEN) +## À faire — toi (hors code) -`src/app/page.tsx` est marqué `'use client'` → perte de perfs (FCP/LCP, signaux de ranking). +### Actions rapides (< 1h chacune) -- [ ] Convertir `page.tsx` en Server Component -- [ ] Extraire les parties interactives (menu langue, `Reveal`, etc.) dans des sous-composants client -- [ ] Vérifier l'amélioration via Lighthouse avant/après +- [ ] **Google Search Console** — soumettre `https://myinfomate.be/sitemap.xml`, vérifier qu'il n'y a pas d'erreurs d'indexation +- [ ] **Bing Webmaster Tools** — même chose (souvent oublié, représente ~10% du trafic desktop) +- [ ] **Lighthouse audit** — ouvrir Chrome DevTools sur myinfomate.be, onglet Lighthouse, mode Mobile → viser > 90. Les points rouges qui sortent sont à corriger en priorité. +- [ ] **LinkedIn entreprise Unov** — si pas encore actif, créer la page et pointer vers myinfomate.be. Google valorise les signaux sociaux B2B. ---- +### Contenu long-tail (impact élevé, effort éditorial) -## 3. JSON-LD sur la home (impact: MOYEN) +C'est le levier le plus fort à long terme. Chaque page = une requête supplémentaire capturée. -Schema.org est présent sur les segments mais pas sur la home. +- [x] **Cas clients** — 3 cas clients créés en 4 langues : Musée de la Fraise (premier client, kiosk + mobile), Fort de Saint-Héribert (mobile offline + iBeacon), Visit Namur (bornes Office du Tourisme). Page listing `/[lang]/cas-clients` + détail `/[lang]/cas-clients/[slug]`, schema.org Article, lien depuis homepage (section dédiée + footer), sitemap mis à jour. +- [ ] **Blog / ressources** — articles fondateurs à écrire : + - "Audioguide vs application visiteur : que choisir en 2026 ?" + - "RGPD et tracking visiteur dans un musée" + - "ROI d'une application visiteur pour un site culturel" + - "Comment digitaliser un parcours de visite" + - "Kiosk tactile vs BYOD : avantages et limites" +- [ ] **Pages comparatives** — très efficaces en B2B SaaS (les gens cherchent "alternative à Smartify") : + - `/fr/comparatif/myinfomate-vs-smartify` + - `/fr/comparatif/myinfomate-vs-stqry` + - `/fr/comparatif/myinfomate-vs-orpheo` -- [ ] Ajouter un schéma `Organization` (Unov) avec `logo`, `url`, `sameAs` (LinkedIn, etc.) -- [ ] Ajouter un schéma `WebSite` -- [ ] Ajouter un schéma `SoftwareApplication` pour MyInfoMate (avec `aggregateRating` si avis disponibles) +### Backlinks off-site (impact élevé, effort relationnel) ---- - -## 4. OG image dédiée (impact: MOYEN) - -Actuellement `/myinfomate-logo.png` est utilisé comme image Open Graph → mauvais rendu sur LinkedIn / WhatsApp / Slack. - -- [ ] Créer une vraie image 1200×630 (titre + visuel produit) -- [ ] Idéalement, utiliser `opengraph-image.tsx` (Next 16) pour génération dynamique -- [ ] Faire une variante par segment (musées, offices tourisme, etc.) - ---- - -## 5. Title et meta description plus stratégiques (impact: MOYEN) - -Le title actuel ("MyInfoMate | La technologie au service de l'expérience visiteur") fait ~63 chars et manque de mots-clés forts. - -- [ ] Réécrire le title de la home avec mots-clés ciblés (ex: "MyInfoMate — Guide visiteur digital pour musées, sites & événements") -- [ ] Réviser titles + descriptions de chaque segment pour viser des requêtes précises (ex: "application musée", "kiosk tactile musée", "guide visiteur tablette") -- [ ] Garder titles < 60 chars et descriptions ~155 chars - ---- - -## 6. Contenu long-tail (impact: ÉLEVÉ à long terme) - -Aujourd'hui : 6 segments + 2 légales = peu de surface SEO. - -- [ ] Créer une section `/cas-clients/[slug]` (un par client référent) -- [ ] Créer une section `/blog/[slug]` ou `/ressources/[slug]` avec 3-5 articles fondateurs : - - [ ] "Audioguide vs application visiteur : que choisir en 2026 ?" - - [ ] "RGPD et tracking visiteur dans un musée" - - [ ] "ROI d'une application visiteur pour un site culturel" - - [ ] "Comment digitaliser un parcours de visite" - - [ ] "Kiosk tactile vs BYOD : avantages et limites" -- [ ] Créer des pages comparatives (très efficace B2B) : - - [ ] `/comparatif/myinfomate-vs-smartify` - - [ ] `/comparatif/myinfomate-vs-stqry` - - [ ] `/comparatif/myinfomate-vs-orpheo` - ---- - -## 7. Détails techniques (impact: FAIBLE à MOYEN) - -- [ ] Vérifier que toutes les `` ont un `alt` descriptif (pas juste "logo") -- [ ] S'assurer qu'il y a un H1 unique et explicite par page -- [ ] Ajouter `priority` sur l'image hero (amélioration LCP) -- [ ] Passer la font Material Symbols de `display=block` à `display=swap` (évite le blocage de rendu) -- [ ] Vérifier le score PageSpeed/Lighthouse (cible: > 90 sur mobile) -- [ ] Soumettre le sitemap dans Google Search Console et Bing Webmaster Tools -- [ ] Mettre en place un suivi des positions (Search Console suffit pour démarrer) - ---- - -## 8. Backlinks / off-site (impact: ÉLEVÉ, hors code) - -Le SEO on-page sera bientôt solide ; le levier suivant est le netlinking. - -- [ ] Référencement dans les annuaires culture/tech belges : - - [ ] Digital Wallonia - - [ ] AWEX - - [ ] hub.brussels -- [ ] Citations dans la presse spécialisée musées : - - [ ] CLIC France - - [ ] ICOM - - [ ] Museumnext -- [ ] Demander un lien retour depuis les sites des clients référents -- [ ] Profil LinkedIn d'entreprise actif (publications régulières → trafic indirect) +- [ ] **Digital Wallonia** — annuaire tech belge, référencement gratuit +- [ ] **AWEX** — agence wallonne à l'exportation, pertinent si tu vises FR/NL/DE +- [ ] **hub.brussels** — si tu vises Bruxelles +- [ ] **CLIC France / Museumnext / ICOM** — presse spécialisée musées, demander une mention ou tribune +- [ ] **Clients référents** — demander un lien retour sur leur site ("Propulsé par MyInfoMate" ou mention dans leur page outils) diff --git a/public/llms.txt b/public/llms.txt new file mode 100644 index 0000000..dc0caf2 --- /dev/null +++ b/public/llms.txt @@ -0,0 +1,85 @@ +# MyInfoMate + +> MyInfoMate is a SaaS platform that digitalizes the visitor experience for cultural venues, tourism offices, parks, hotels, and events. It enables venue managers to create, manage and distribute interactive content on mobile apps, tablet kiosks, and web — with no coding required. + +Developed by **Unov**, based in Namur, Wallonia (Belgium). +Contact: contact@unov.be +Website: https://myinfomate.be + +## What is MyInfoMate? + +MyInfoMate is a content management and distribution platform (CMS) for any venue that welcomes visitors. It provides: +- A back-office web interface to create and manage interactive visitor journeys +- A white-label native mobile app (iOS & Android) for visitors on their own devices (BYOD) +- A tablet kiosk app for fixed installations +- A web app accessible from any browser, no install needed + +All deployments are managed from a single CMS. Content can be updated in real time from a browser. + +## Key Features + +- **Interactive Maps**: Real-time geolocation, clickable points of interest, route navigation +- **Games & Quizzes**: Treasure hunts, knowledge quizzes, gamified trails, escape games with narrative and riddles +- **Audio & Video**: Immersive audio guides, HD video, multimedia content +- **Articles & PDF**: Descriptive sheets, brochures, digitized catalogs +- **Agenda & Events**: Dynamic event calendar and scheduling +- **Offline Access**: Automatic content download for areas without Wi-Fi or 4G +- **Web & Social Content**: Embedded forms, social feeds, external links +- **Statistics & Data**: Visitor behavior analytics, engagement tracking, content popularity +- **Escape Game & Trails**: The only CMS on the market with a full escape game module built in +- **AI Visitor Assistant**: Generative AI guide available 24/7, multilingual, scoped strictly to the venue's content +- **Push Notifications**: Real-time alerts to visitors +- **Automatic Translation**: Content auto-translated across languages +- **White Label**: Full interface customization with venue's brand, logo, colors, and typography + +## Deployment Modes + +1. **Mobile App (BYOD)** — visitors use their own smartphone; white-label app published on App Store & Google Play +2. **Tablet Kiosk** — robust pre-installed app on venue-managed tablet fleets +3. **Web App** — accessible from any modern browser, no install required +4. **VR & AR** (coming soon) — immersive experiences on Meta Quest and connected glasses + +## AI Assistant + +The MyInfoMate AI assistant is a generative AI guide integrated into the visitor app: +- Answers visitor questions in their own language (French, English, Dutch, German, and more) +- Knows the venue's content: points of interest, exhibitions, services +- Maintains conversation context throughout the visit +- Strictly scoped to the venue — no off-topic responses +- Available 24/7 + +## Target Venues + +- **Museums & Heritage**: digitize collections, create multimedia trails, multilingual smart guide +- **Tourism Offices**: city/region maps, event agendas, 24/7 AI assistant +- **Parks & Natural Sites**: trail markers, biodiversity content, offline quizzes and treasure hunts +- **Hotels & Leisure**: interactive estate map, services, virtual concierge +- **Events & Exhibitions**: festivals, trade shows, fairs — interactive maps, real-time agenda +- **Education & Culture**: campuses, science centers, libraries — guided content per space + +## Customer References + +Real venues using MyInfoMate (Belgium): + +- **Musée de la Fraise de Wépion** (2021) — first MyInfoMate customer. Started with a tablet kiosk solution (the original use case that drove the creation of MyInfoMate), then extended to a visitor mobile app covering the museum and the fruit garden trail. Demonstrates the no-code CMS autonomy for a small museum team. Case study: https://myinfomate.be/fr/cas-clients/musee-de-la-fraise + +- **Fort de Saint-Héribert** (2023) — first mobile deployment of MyInfoMate, on a historic underground fort. 100% offline mode (no Wi-Fi possible in the galleries), iBeacon beacons for indoor location tracking, QR codes, audio guide, iOS + Android. Case study: https://myinfomate.be/fr/cas-clients/fort-de-saint-heribert + +- **Visit Namur** (Tourist Office of Namur, 2024) — interactive tablet kiosks deployed at the tourism office. Interactive city map with geolocated points of interest, real-time events agenda, itinerary planning. Case study: https://myinfomate.be/fr/cas-clients/visit-namur + +All case studies index: https://myinfomate.be/fr/cas-clients + +## Pricing + +Subscription-based (monthly, excl. VAT). Starting from **€39/month**. +- No commitment for basic plans +- Setup fee + 12-month commitment for native mobile app plans +- Enterprise: custom quote for multi-site organizations or specific needs (custom development, enhanced SLA) +- Multi-year contracts (2–3 years) available with preferential rates + +## Company + +**Unov** — software company based in Namur, Belgium. +MyInfoMate is already deployed in Namur and across Wallonia. +Email: contact@unov.be +Website: https://myinfomate.be diff --git a/src/app/[lang]/HomeClient.tsx b/src/app/[lang]/HomeClient.tsx index 2f47291..5be0779 100644 --- a/src/app/[lang]/HomeClient.tsx +++ b/src/app/[lang]/HomeClient.tsx @@ -1,313 +1,19 @@ -'use client'; - -import React, { useState, useEffect, useRef } from 'react'; import Image from 'next/image'; -import { useRouter } from 'next/navigation'; import { resolveImage } from '@/data/stitch-images'; import translations, { Language } from '@/data/translations'; - -const MODULE_META = [ - { id: 'map', icon: 'map', theme: 'rose' }, - { id: 'quiz', icon: 'sports_esports', theme: 'rose' }, - { id: 'video', icon: 'videocam', theme: 'rose' }, - { id: 'article', icon: 'article', theme: 'rose' }, - { id: 'agenda', icon: 'calendar_month', theme: 'rose' }, - { id: 'offline', icon: 'download_for_offline', theme: 'rose' }, - { id: 'web', icon: 'public', theme: 'rose' }, - { id: 'stats', icon: 'monitoring', theme: 'rose' }, - { id: 'escapegame', icon: 'explore', theme: 'rose' }, -]; - -const LANGUAGES: { code: Language; label: string; name: string }[] = [ - { code: 'fr', label: 'FR', name: 'Français' }, - { code: 'en', label: 'EN', name: 'English' }, - { code: 'nl', label: 'NL', name: 'Nederlands' }, - { code: 'de', label: 'DE', name: 'Deutsch' }, -]; - -function Reveal({ children, delay = 0, className = '' }: { children: React.ReactNode; delay?: number; className?: string }) { - const ref = React.useRef(null); - const [visible, setVisible] = React.useState(false); - - React.useEffect(() => { - const el = ref.current; - if (!el) return; - const observer = new IntersectionObserver( - ([entry]) => { if (entry.isIntersecting) setVisible(true); }, - { threshold: 0.12 } - ); - observer.observe(el); - return () => observer.disconnect(); - }, []); - - return ( -
- {children} -
- ); -} +import { getAllCasClients } from '@/data/cas-clients'; +import NavBar from '@/components/NavBar'; +import Reveal from '@/components/Reveal'; +import ModulesSection from '@/components/ModulesSection'; +import ContactForm from '@/components/ContactForm'; export default function Home({ lang }: { lang: Language }) { - const router = useRouter(); - const setLang = (next: Language) => { - if (next !== lang) router.push(`/${next}`); - }; - const [formData, setFormData] = useState({ - firstName: '', - lastName: '', - email: '', - message: '' - }); - - const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle'); - const [activeModule, setActiveModule] = useState(0); - const [isMenuOpen, setIsMenuOpen] = useState(false); - const [isLangOpen, setIsLangOpen] = useState(false); - const langDropdownRef = useRef(null); - const [navVisible, setNavVisible] = useState(true); - const lastScrollY = useRef(0); - const t = translations[lang]; - - const modules = MODULE_META.map((meta, i) => ({ - ...meta, - title: t.modules.items[i].title, - description: t.modules.items[i].description, - value: t.modules.items[i].value, - })); - - // Hide navbar on scroll down, show on scroll up - useEffect(() => { - const handleScroll = () => { - const currentY = window.scrollY; - setNavVisible(currentY < lastScrollY.current || currentY < 10); - lastScrollY.current = currentY; - }; - window.addEventListener('scroll', handleScroll, { passive: true }); - return () => window.removeEventListener('scroll', handleScroll); - }, []); - - // Lock body scroll when mobile menu is open - useEffect(() => { - if (isMenuOpen) { - document.body.style.overflow = 'hidden'; - } else { - document.body.style.overflow = 'auto'; - } - return () => { document.body.style.overflow = 'auto'; }; - }, [isMenuOpen]); - - // Close lang dropdown on outside click - useEffect(() => { - const handler = (e: MouseEvent) => { - if (langDropdownRef.current && !langDropdownRef.current.contains(e.target as Node)) { - setIsLangOpen(false); - } - }; - document.addEventListener('mousedown', handler); - return () => document.removeEventListener('mousedown', handler); - }, []); - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setStatus('loading'); - - try { - const response = await fetch("https://formspree.io/f/xbdaajlo", { - method: "POST", - headers: { - "Content-Type": "application/json", - "Accept": "application/json" - }, - body: JSON.stringify(formData) - }); - - if (response.ok) { - setStatus('success'); - setFormData({ firstName: '', lastName: '', email: '', message: '' }); - } else { - setStatus('error'); - } - } catch (error) { - setStatus('error'); - } - }; - - const handleChange = (e: React.ChangeEvent) => { - const { name, value } = e.target; - setFormData(prev => ({ ...prev, [name]: value })); - }; - - const scrollToContact = (e: React.MouseEvent) => { - e.preventDefault(); - document.getElementById('contact')?.scrollIntoView({ behavior: 'smooth' }); - }; + const casClients = getAllCasClients(); return (
- {/* Top Navigation */} -
-
- - MyInfoMate Logo - MyInfoMate - - -
- - {t.nav.login} - - - {/* Language selector - desktop */} -
- - {isLangOpen && ( -
- {LANGUAGES.map((l) => ( - - ))} -
- )} -
- -
-
- -
- - {/* Mobile Menu Backdrop */} -
setIsMenuOpen(false)} - /> - - {/* Mobile Menu Sidebar */} -
-
- {/* Close Button Header */} -
- -
- - -
-
+
{/* Hero Section */} @@ -327,19 +33,17 @@ export default function Home({ lang }: { lang: Language }) { {t.hero.subtitle}

- +
- {/* UI Mockup Group - Improved for Mobile */}
- {/* Interactive Map Tablet Preview */}
{t.mockup.mapSubtitle}

- - {/* Agenda Mobile Preview - Hidden or repositioned on very small screens */}
- App screenshot + MyInfoMate agenda module on mobile
- - {/* Tours Mobile Preview */}
- App screenshot + MyInfoMate tours module on mobile
@@ -379,7 +79,6 @@ export default function Home({ lang }: { lang: Language }) {

{t.strategic.sectionTitle}

- {/* Feature 1 */}
sync @@ -389,7 +88,6 @@ export default function Home({ lang }: { lang: Language }) {

{t.strategic.feature1Desc}

- {/* Feature 2 */}
code_off @@ -399,7 +97,6 @@ export default function Home({ lang }: { lang: Language }) {

{t.strategic.feature2Desc}

- {/* Feature 3 */}
download_for_offline @@ -409,7 +106,6 @@ export default function Home({ lang }: { lang: Language }) {

{t.strategic.feature3Desc}

- {/* Feature 4 */}
dashboard_customize @@ -423,18 +119,15 @@ export default function Home({ lang }: { lang: Language }) {
- {/* Deployment Modes Section */} + {/* Deployment Modes */}

{t.deployment.sectionLabel}

{t.deployment.sectionTitle}

-

- {t.deployment.sectionDesc} -

+

{t.deployment.sectionDesc}

- {/* Mode 1: Mobile BYOD */}
smartphone @@ -442,7 +135,6 @@ export default function Home({ lang }: { lang: Language }) {

{t.deployment.mode1Title}

{t.deployment.mode1Desc}

- {/* Mode 2: Kiosk Tablet */}
tablet_android @@ -450,7 +142,6 @@ export default function Home({ lang }: { lang: Language }) {

{t.deployment.mode2Title}

{t.deployment.mode2Desc}

- {/* Mode 3: Web App */}
language @@ -458,9 +149,8 @@ export default function Home({ lang }: { lang: Language }) {

{t.deployment.mode3Title}

{t.deployment.mode3Desc}

- {/* Mode 4: Future VR/AR */}
-
+
{t.deployment.comingSoon}
@@ -473,178 +163,8 @@ export default function Home({ lang }: { lang: Language }) {
- {/* Interactive Modules Section (Module Explorer) */} -
-
- -

{t.modules.sectionLabel}

-

{t.modules.sectionTitle}

-

- {t.modules.sectionDesc} -

-
- -
- {/* Sidebar (Tabs) */} - -
-
-
- {modules.map((module, index) => ( - - ))} -
-
- - {/* Main Display */} - - - {/* Mobile: single centered card */} -
-
-
- {modules[activeModule].icon} - {modules[activeModule].title} -
-

{modules[activeModule].title}

-

{modules[activeModule].description}

-
-

- verified - {modules[activeModule].value} -

-
-
-
-
-
-
-
-
-
- {modules[activeModule].icon} -
- {modules[activeModule].title} -
-
-
- {modules[activeModule].icon} -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - {/* Desktop: original layout + enobase-style 3D phone stack */} -
- {/* Description */} -
-
- {modules[activeModule].icon} - {modules[activeModule].title} -
-

{modules[activeModule].title}

-

{modules[activeModule].description}

-
-

- verified - {t.modules.valueLabel} {modules[activeModule].value} -

-
-
- - {/* Enobase-style 3D horizontal perspective phone stack */} -
-
- {modules.map((module, index) => { - const deckPos = (index - activeModule + modules.length) % modules.length; - const isActive = deckPos === 0; - return ( -
-
-
-
-
-
-
- {module.icon} -
- {module.title} -
-
-
- {module.icon} -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ); - })} -
-
-
- -
-
-
-
+ {/* Module Explorer */} + {/* AI Assistant */}
@@ -665,12 +185,11 @@ export default function Home({ lang }: { lang: Language }) {

{t.ai.desc}

- {/* Chat mockup */}
- smart_toy + smart_toy

{t.ai.mockupName}

@@ -681,37 +200,30 @@ export default function Home({ lang }: { lang: Language }) {
- smart_toy -
-
- {t.ai.mockupMsg1} + smart_toy
+
{t.ai.mockupMsg1}
-
- {t.ai.mockupMsg2} -
+
{t.ai.mockupMsg2}
- smart_toy -
-
- {t.ai.mockupMsg3} + smart_toy
+
{t.ai.mockupMsg3}

{t.ai.mockupInputPlaceholder}

- send + send
- {/* Features grid */}
@@ -746,7 +258,7 @@ export default function Home({ lang }: { lang: Language }) {
- {/* White Label / Value Proposition */} + {/* White Label */}
@@ -898,7 +410,63 @@ export default function Home({ lang }: { lang: Language }) {
- {/* Pricing Section */} + {/* Cas Clients - Social Proof */} +
+
+
+
+
+
+ +

{t.casClients.sectionLabel}

+

{t.casClients.title}

+

{t.casClients.subtitle}

+
+ +
+ {casClients.map((client, i) => { + const ct = client.translations[lang]; + return ( + + +
+ + {ct.badge} + + {client.year} +
+

+ {client.client} +

+

+ {ct.description} +

+ + {t.casClients.readCase} + arrow_forward + +
+
+ ); + })} +
+ + + + {t.casClients.ctaAll} + arrow_forward + + +
+
+ + {/* Pricing */}
@@ -908,10 +476,7 @@ export default function Home({ lang }: { lang: Language }) {

{t.pricing.sectionDesc}

-
- - {/* Essentiel */}
@@ -947,13 +512,11 @@ export default function Home({ lang }: { lang: Language }) { ))} - +
- - {/* Pro */}
@@ -996,13 +559,11 @@ export default function Home({ lang }: { lang: Language }) { ))} - +
- - {/* Bundle */}
@@ -1047,13 +608,11 @@ export default function Home({ lang }: { lang: Language }) { {t.pricing.features.autoTranslation} - +
- - {/* Enterprise */}
@@ -1099,14 +658,12 @@ export default function Home({ lang }: { lang: Language }) { ))} - +
-
-
@@ -1116,15 +673,14 @@ export default function Home({ lang }: { lang: Language }) {

{t.pricing.multiYearDesc}

- +
-
@@ -1139,19 +695,17 @@ export default function Home({ lang }: { lang: Language }) {

{t.cta.titleBefore}{t.cta.titleHighlight}{t.cta.titleAfter}

-

- {t.cta.subtitle} -

+

{t.cta.subtitle}

- + {t.cta.button2} @@ -1164,7 +718,6 @@ export default function Home({ lang }: { lang: Language }) {
-
@@ -1172,94 +725,7 @@ export default function Home({ lang }: { lang: Language }) {

{t.contact.title}

{t.contact.subtitle}

- - {status === 'success' ? ( -
-
- task_alt -
-

{t.contact.successTitle}

-

{t.contact.successDesc}

- -
- ) : ( -
- {status === 'error' && ( -
- error - {t.contact.errorMsg} -
- )} -
-
- - -
-
- - -
-
-
- - -
-
- - -
- -
- )} +
@@ -1280,9 +746,7 @@ export default function Home({ lang }: { lang: Language }) { /> MyInfoMate
-

- {t.footer.desc} -

+

{t.footer.desc}

+
- {/*

Produit

+

{t.footer.casClientsLink}

*/} -
-
- {/*

Entreprise

- */} + {casClients.map((c) => ( +
  • + + {c.client} + +
  • + ))} +
  • + + {t.casClients.ctaAll} → + +
  • +

    {t.footer.contactTitle}

    diff --git a/src/app/[lang]/[segment]/opengraph-image.tsx b/src/app/[lang]/[segment]/opengraph-image.tsx new file mode 100644 index 0000000..ac1442a --- /dev/null +++ b/src/app/[lang]/[segment]/opengraph-image.tsx @@ -0,0 +1,143 @@ +import { ImageResponse } from 'next/og'; +import { isLocale } from '@/i18n'; +import { getSegmentData } from '@/data/segments'; + +export const runtime = 'edge'; +export const size = { width: 1200, height: 630 }; +export const contentType = 'image/png'; + +export default function Image({ params }: { params: { lang: string; segment: string } }) { + const lang = isLocale(params.lang) ? params.lang : 'fr'; + const data = getSegmentData(params.segment); + const badge = data?.translations[lang].hero.badge ?? 'MyInfoMate'; + const title = data?.meta[lang].title ?? 'MyInfoMate'; + const description = data?.meta[lang].description ?? ''; + + return new ImageResponse( + ( +
    + {/* Cyan glow top-left */} +
    + {/* Purple glow bottom-right */} +
    + + {/* Content */} +
    + {/* Segment badge */} +
    +
    + + {badge} + +
    + + {/* Product name */} +
    + MyInfoMate +
    + + {/* Page title */} +
    + {title.replace(' | MyInfoMate', '').replace(' — MyInfoMate', '')} +
    +
    + + {/* Bottom URL */} +
    + myinfomate.be +
    +
    + ), + { ...size } + ); +} diff --git a/src/app/[lang]/cas-clients/[slug]/page.tsx b/src/app/[lang]/cas-clients/[slug]/page.tsx new file mode 100644 index 0000000..aab6552 --- /dev/null +++ b/src/app/[lang]/cas-clients/[slug]/page.tsx @@ -0,0 +1,200 @@ +import type { Metadata } from 'next'; +import { notFound } from 'next/navigation'; +import { LOCALES, LOCALE_HTML_LANG, LOCALE_OG, DEFAULT_LOCALE, isLocale } from '@/i18n'; +import { getCasClientData, getAllCasClientSlugs } from '@/data/cas-clients'; + +const SITE_URL = 'https://myinfomate.be'; + +export function generateStaticParams() { + const slugs = getAllCasClientSlugs(); + return LOCALES.flatMap((lang) => slugs.map((slug) => ({ lang, slug }))); +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ lang: string; slug: string }>; +}): Promise { + const { lang, slug } = await params; + if (!isLocale(lang)) return {}; + const data = getCasClientData(slug); + if (!data) return {}; + const m = data.meta[lang]; + const languages = Object.fromEntries( + LOCALES.map((l) => [LOCALE_HTML_LANG[l], `/${l}/cas-clients/${slug}`]) + ); + return { + title: m.title, + description: m.description, + openGraph: { + title: m.title, + description: m.description, + url: `${SITE_URL}/${lang}/cas-clients/${slug}`, + siteName: 'MyInfoMate', + locale: LOCALE_OG[lang], + type: 'website', + }, + alternates: { + canonical: `/${lang}/cas-clients/${slug}`, + languages: { ...languages, 'x-default': `/${DEFAULT_LOCALE}/cas-clients/${slug}` }, + }, + }; +} + +export default async function CasClientPage({ + params, +}: { + params: Promise<{ lang: string; slug: string }>; +}) { + const { lang, slug } = await params; + if (!isLocale(lang)) notFound(); + const data = getCasClientData(slug); + if (!data) notFound(); + + const t = data.translations[lang]; + + const caseStudySchema = { + '@context': 'https://schema.org', + '@type': 'Article', + headline: t.headline, + description: t.description, + author: { '@type': 'Organization', name: 'Unov', url: SITE_URL }, + publisher: { '@type': 'Organization', name: 'MyInfoMate', url: SITE_URL }, + datePublished: `${data.year}-01-01`, + url: `${SITE_URL}/${lang}/cas-clients/${slug}`, + }; + + return ( + <> +