Article SEO SEO Technique
SEO Local, Local Inventory et Inventaire local : concept, enjeux et bonnes pratiques

SEO Local, Local Inventory et Inventaire local : le guide complet

Sommaire de l'article

Introduction

L’optimisation pour les moteurs de recherche (SEO) est devenue une discipline stratégique pour toute entreprise souhaitant accroître sa visibilité en ligne et générer des ventes de manière durable. Parmi les nombreux leviers disponibles, le SEO local et le concept d’inventaire local (ou Local Inventory) occupent aujourd’hui une place centrale pour les commerces physiques, les enseignes omnicanales et les e-commerçants disposant de points de vente.

Les recherches locales représentent désormais une part majeure des recherches effectuées sur Google, avec près de la moitié des requêtes qui comportent une intention locale. Une proportion significative de ces recherches aboutit à une visite en magasin dans les 24 heures, et une part importante à un achat. Ces chiffres illustrent à quel point la maîtrise du SEO local et de l’inventaire local n’est plus une option, mais un levier de croissance concret.

Dans ce contexte, le Local Inventory devient un élément clé pour connecter le stock disponible dans vos magasins aux recherches des consommateurs à proximité. Lorsqu’il est correctement structuré et synchronisé avec vos fiches Google Business Profile (anciennement Google My Business) et vos pages produits, il permet à Google, mais aussi à d’autres plateformes, d’afficher des résultats enrichis du type « Disponible près de chez vous » ou « En stock dans ce magasin ».

Cet article propose une exploration détaillée du SEO local et de l’inventaire local : définitions, enjeux business, bonnes pratiques techniques, intégration dans votre stratégie omnicanale et outils à utiliser. L’objectif est de vous fournir une feuille de route claire et actionnable pour exploiter pleinement ce levier.

Concepts clés

Qu’est-ce que le SEO Local ?

Le SEO local regroupe l’ensemble des techniques qui visent à optimiser votre présence sur les moteurs de recherche pour des requêtes associées à une localisation précise (ville, quartier, zone de chalandise, « près de moi », etc.). Il ne s’agit pas uniquement de faire apparaître votre site web dans les résultats naturels, mais aussi d’optimiser :

  • votre fiche Google Business Profile (ex-Google My Business) ;
  • votre présence sur Google Maps ;
  • vos pages de magasins ou de points de vente ;
  • vos avis clients et votre e-réputation locale ;
  • vos contenus orientés par ville, région, zone desservie ou secteur.

Contrairement au SEO dit « global », qui cible une audience large et non géolocalisée, le SEO local se concentre sur la proximité et la pertinence géographique. Il vise à répondre à des recherches telles que « magasin de bricolage à Lyon », « restaurant italien près de moi » ou « service de réparation smartphone Paris 15 ».

Cette approche est particulièrement stratégique pour les entreprises disposant d’un ancrage physique : commerces de proximité, franchises, réseaux de magasins, artisans, professions libérales, restaurants, hôtels, etc. Elle concerne également les e-commerçants qui proposent le retrait en magasin (click & collect), le drive ou la livraison locale.

Qu’est-ce que l’Inventaire Local (Local Inventory) ?

L’inventaire local, ou Local Inventory, désigne l’ensemble des informations relatives aux produits disponibles dans un ou plusieurs magasins physiques, associées à une localisation précise. Il inclut notamment :

  • le SKU ou identifiant produit ;
  • le magasin ou le point de vente concerné ;
  • le niveau de stock (en stock, stock limité, en rupture, sur commande) ;
  • le prix local (qui peut différer du prix en ligne) ;
  • les options de retrait (retrait en magasin, click & collect, drive) ;
  • éventuellement les délais de disponibilité.

Sur Google, ce concept se matérialise notamment à travers les annonces pour un inventaire local (Local Inventory Ads), les fiches produits locales et certains affichages dans le Local Pack ou sur Google Maps, où l’internaute peut voir si un produit spécifique est disponible à proximité et dans quel magasin.

Concrètement, lorsqu’un utilisateur recherche un produit précis, par exemple « baskets de course Nike homme Marseille », Google peut utiliser les données d’inventaire local pour :

  • identifier quels magasins possèdent ce modèle en stock ;
  • afficher des informations du type « En stock à 1,2 km » ;
  • indiquer le prix pratiqué localement ;
  • proposer une option de retrait rapide.

L’inventaire local est donc le pont de données entre votre stock réel en magasin et l’interface de recherche du consommateur. Une entreprise qui ne remonte pas ces informations vers Google perd une partie importante du potentiel de la recherche locale orientée produit.

Lien entre SEO Local et Inventaire Local

Le lien entre SEO local et inventaire local est direct : l’un enrichit l’autre. Le SEO local permet de rendre visible votre entreprise, vos points de vente et vos services dans une zone géographique donnée, tandis que l’inventaire local permet de faire remonter des informations produit précises pour ces mêmes points de vente.

Lorsqu’il est bien configuré, un inventaire local complet et à jour permet aux moteurs de recherche de comprendre :

  • quels produits sont disponibles dans quels magasins ;
  • à quel prix et avec quel délai d’obtention ;
  • quelles sont les options de retrait ou de livraison locale ;
  • comment ces informations se combinent avec la géolocalisation de l’utilisateur.

Cette approche ne se limite pas aux pure players e-commerce. Même une entreprise historiquement « physique » peut tirer parti de l’inventaire local en :

  • connectant son logiciel de caisse ou son ERP à un flux d’inventaire ;
  • exposant ce flux via un flux produit (product feed) vers Google Merchant Center ;
  • mettant à jour en temps quasi réel les disponibilités par magasin.

Le bénéfice est double : vous améliorez la pertinence de vos résultats pour l’utilisateur, et vous augmentez la probabilité de visite en magasin, une partie importante des recherches locales se traduisant par un déplacement physique ou un appel téléphonique.

Enjeux business du SEO Local et de l’Inventaire Local

Au-delà de la dimension purement technique, l’intégration du SEO local et de l’inventaire local a un impact concret sur le chiffre d’affaires. Les études disponibles montrent qu’une part très significative des recherches locales aboutit à :

  • une visite en magasin dans les 24 heures suivant la recherche ;
  • une prise de contact (appel, formulaire, demande d’itinéraire) ;
  • un achat, en ligne ou hors ligne.

Les recherches incluant des expressions telles que « près de moi », « à proximité », « ouvert maintenant » ou associées à un nom de ville sont souvent fortement intentionnistes : le prospect n’est plus simplement en phase d’inspiration, il est proche du passage à l’action.

Pour un commerce local, un réseau de points de vente ou une enseigne de distribution, les bénéfices d’une stratégie bien menée sont notamment :

  • une augmentation du trafic qualifié en magasin ;
  • un meilleur taux de conversion des recherches locales en ventes ;
  • une réduction des coûts d’acquisition par rapport à certains canaux payants ;
  • une meilleure exploitation des stocks existants (écoulement des stocks locaux, réduction des surstocks et ruptures visibles) ;
  • une amélioration de la notoriété locale et de la confiance (grâce aux avis et aux informations fiables).

Bonnes pratiques SEO pour exploiter l’Inventaire Local

Optimiser le contenu pour l’Inventaire Local

Pour que votre inventaire local soit pleinement exploité par Google et les autres moteurs de recherche, vos contenus doivent être structurés de manière à rendre explicite le lien entre un produit, un lieu et une intention locale. Cela concerne à la fois vos pages produits et vos pages de magasins.

  • Intégrez des mots-clés locaux dans vos titres, balises , <h1>, <h2 id="et-descriptions-combinez-le-nom-du-produit-ou-de-la-categorie-avec-le-nom-de-la-ville-du-quartier-ou"> et descriptions : combinez le nom du produit ou de la catégorie avec le nom de la ville, du quartier ou de la zone.</li> <li><strong>Précisez la disponibilité locale</strong> dans le contenu : mention explicite du type « Disponible dans notre magasin de Lyon Part-Dieu » ou « En stock dans nos boutiques de Bordeaux et Mérignac ».</li> <li><strong>Créez des pages dédiées par magasin</strong>, incluant une sélection de produits phares disponibles localement, avec un maillage interne clair vers les pages produits.</li> <li><strong>Optimisez vos URL</strong> pour refléter la dimension locale : par exemple, <em>/magasins/lyon/produits/baskets-running/</em> plutôt qu’une URL générique.</li> <li><strong>Utilisez des blocs de contenu contextualisés</strong> : horaires, parking, accès en transports, particularités locales, services spécifiques du magasin (atelier, SAV, location, etc.).</li> </ul> <p>Plus vos pages mettent en avant de façon explicite la combinaison <strong>produit + lieu</strong>, plus elles ont de chances d’apparaître pour des requêtes géolocalisées à forte intention d’achat.</p> <h3>Améliorer la structure des données pour l’Inventaire Local</h2> <p>La structure des données est un pilier fondamental de l’optimisation de l’inventaire local. En utilisant les <strong>données structurées</strong> (JSON-LD, Microdata ou RDFa), vous aidez les moteurs de recherche à comprendre précisément :</p> <ul> <li>quels sont vos <strong>produits</strong> ;</li> <li>où ils sont <strong>disponibles</strong> ;</li> <li>s’ils sont <strong>en stock</strong> ;</li> <li>quelles sont les <strong>caractéristiques</strong> (taille, couleur, marque, etc.).</li> </ul> <p>Quelques recommandations concrètes :</p> <ul> <li>Utilisez le schéma <strong>Product</strong> pour décrire vos produits (nom, description, image, SKU, marque, catégorie, attributs, prix, etc.).</li> <li>Ajoutez le type <strong>Offer</strong> pour préciser les informations d’offre : prix, devise, disponibilité, conditions de vente.</li> <li>S’il existe des variations par magasin (prix ou disponibilité), faites le lien entre le produit et les points de vente via les schémas <strong>LocalBusiness</strong> ou <strong>Store</strong>.</li> <li>Spécifiez clairement les <strong>magasins</strong> où chaque produit est disponible, soit dans la page produit, soit dans des pages de magasins enrichies.</li> <li>Veillez à ce que les données structurées soient <strong>cohérentes</strong> avec ce qui est affiché à l’écran (prix, disponibilité, nom du produit, etc.).</li> </ul> <p>Un balisage rigoureux améliore vos chances d’apparaître dans des résultats enrichis (rich results) et facilite l’intégration avec des fonctionnalités avancées comme les annonces d’inventaire local.</p> <h3 id="creer-du-contenu-de-qualite-oriente-local">Créer du contenu de qualité orienté local</h3> <p>Le contenu reste un levier essentiel pour renforcer votre visibilité locale et donner du contexte à votre inventaire. Pour exploiter au mieux ce levier :</p> <ul> <li><strong>Rédigez des descriptions complètes</strong> pour chaque produit, en intégrant les bénéfices concrets pour un usage local (météo, typologie de clientèle, habitudes de consommation de la ville, etc.).</li> <li><strong>Ajoutez des images de haute qualité</strong>, optimisées (poids, attribut alt, légendes) et, si possible, mettant en scène le produit dans vos magasins ou dans des contextes locaux.</li> <li><strong>Créez du contenu éditorial local</strong> : guides d’achat par ville, inspirations saisonnières, cas clients locaux, présentations d’équipes de magasin.</li> <li><strong>Intégrez les avis clients locaux</strong> et les témoignages liés à un magasin spécifique ou à l’usage du produit dans une région donnée.</li> <li><strong>Alimentez régulièrement vos contenus</strong> pour refléter les nouveautés, les promotions spécifiques à un magasin, les opérations locales ou les événements (portes ouvertes, ateliers, démonstrations).</li> </ul> <p>Ce contenu riche et contextualisé ne sert pas uniquement le référencement : il rassure l’utilisateur sur la pertinence de votre offre pour sa situation et il augmente la probabilité de conversion.</p> <h3 id="gestion-des-mises-a-jour-de-l-inventaire-local-en-temps-quasi-reel">Gestion des mises à jour de l’inventaire local en temps (quasi) réel</h3> <p>Un des défis majeurs de l’optimisation de l’inventaire local est la <strong>fraîcheur des données</strong>. Un internaute qui se déplace en magasin après avoir vu « En stock » sur Google et découvre une rupture risque d’être fortement déçu, et la confiance s’en trouvera affectée.</p> <p>Pour limiter ce risque, il est essentiel de mettre en place des processus et des outils permettant de maintenir des données d’inventaire <strong>à jour</strong> :</p> <ul> <li>Déployez un <strong>système de gestion des stocks centralisé</strong> (ERP, OMS, ou solution spécialisée) qui agrège l’inventaire de tous les magasins en temps réel ou quasi réel.</li> <li>Intégrez ce système à votre <strong>plateforme e-commerce</strong> et à votre <strong>Google Merchant Center</strong> pour alimenter les flux d’inventaire local.</li> <li>Automatisez la <strong>mise à jour des flux</strong> d’inventaire (via API ou fichiers mis à jour régulièrement) pour limiter les interventions manuelles et les erreurs.</li> <li>Planifiez des <strong>audits réguliers</strong> de vos données : cohérence entre le stock théorique et le stock réel, contrôle de la qualité des fiches produits, vérification des correspondances SKU.</li> <li>Prévoyez des <strong>seuils d’alerte</strong> pour les produits à rotation rapide, afin d’éviter l’affichage « En stock » lorsque le niveau de stock est trop faible.</li> </ul> <p>Une gestion rigoureuse de la fraîcheur des données améliore l’expérience utilisateur et protège votre réputation en ligne. Elle est également indispensable pour que les outils de Google puissent continuer à faire confiance à vos flux d’inventaire.</p> <h2 id="integrer-l-inventaire-local-dans-votre-ecosysteme-digital">Intégrer l’Inventaire Local dans votre écosystème digital</h2> <h3 id="role-de-google-business-profile-et-de-google-maps">Rôle de Google Business Profile et de Google Maps</h3> <p>Votre <strong>profil d’entreprise Google</strong> (Google Business Profile) est au cœur de votre visibilité locale. Il permet d’afficher des informations clés telles que :</p> <ul> <li>nom du magasin, adresse, numéro de téléphone ;</li> <li>horaires d’ouverture, jours de fermeture exceptionnels ;</li> <li>photos du lieu, de l’équipe, des produits ;</li> <li>avis clients et réponses de l’entreprise ;</li> <li>lien vers le site, itinéraire, options de prise de rendez-vous.</li> </ul> <p>Dans une logique d’inventaire local, ce profil peut également être connecté à des informations produits, permettant d’afficher certains articles populaires comme étant <strong>disponibles en magasin</strong>. Plus votre profil est complet, mis à jour et cohérent avec les informations de votre site, plus il contribue positivement à votre référencement local.</p> <h3 id="pages-magasins-et-maillage-interne">Pages magasins et maillage interne</h3> <p>Les <strong>pages magasins</strong> jouent un rôle central dans le déploiement d’une stratégie SEO local + inventaire local :</p> <ul> <li>Créez une <strong>page dédiée pour chaque point de vente</strong>, avec l’adresse exacte, les coordonnées, un plan d’accès, des photos et une description unique du magasin.</li> <li>Ajoutez une section « <strong>Produits phares disponibles dans ce magasin</strong> » ou des blocs listant les catégories de produits clés disponibles localement.</li> <li>Reliez chaque page magasin à vos <strong>pages produits</strong> correspondantes et, inversement, faites des liens depuis les pages produits vers les magasins où le produit est disponible.</li> <li>Structurez un <strong>maillage interne</strong> logique : pages villes > pages magasins > pages catégories > pages produits.</li> <li>Optimisez chaque page magasin avec des <strong>mots-clés locaux</strong> et un contenu unique (ne pas dupliquer le même texte pour tous les magasins en ne changeant que la ville).</li> </ul> <p>Cette architecture permet aux moteurs de recherche de comprendre la structure de votre réseau et d’associer plus facilement vos produits à des emplacements physiques précis.</p> <h3 id="experience-utilisateur-omnicanale">Expérience utilisateur omnicanale</h3> <p>L’intégration de l’inventaire local ne doit pas être pensée uniquement sous l’angle SEO, mais dans une logique <strong>omnicanale</strong> :</p> <ul> <li>Proposez le <strong>retrait en magasin</strong> (click & collect) directement depuis vos pages produits, avec l’indication de la disponibilité par magasin.</li> <li>Offrez la possibilité de <strong>réserver un produit</strong> en magasin avant de se déplacer, lorsque cela est pertinent.</li> <li>Synchronisez vos campagnes publicitaires locales (SEA, social ads) avec vos données d’inventaire pour éviter de promouvoir des produits en rupture locale.</li> <li>Affichez clairement les <strong>délais</strong> et les <strong>conditions</strong> de retrait ou de livraison locale (par exemple, retrait en 2 heures, livraison en 24 heures dans un rayon défini).</li> <li>Assurez une <strong>cohérence tarifaire</strong> entre le site et les magasins, ou explicitez les différences si des prix locaux spécifiques s’appliquent.</li> </ul> <p>Une expérience fluide entre la recherche en ligne et la visite en magasin renforce l’efficacité de votre stratégie de SEO local et d’inventaire local et augmente la satisfaction globale de vos clients.</p> <h2 id="outils-et-ressources-pour-gerer-le-seo-local-et-l-inventaire-local">Outils et ressources pour gérer le SEO Local et l’Inventaire Local</h2> <h3 id="outils-reconnus-pour-le-seo-local">Outils reconnus pour le SEO local</h3> <p>Pour mettre en œuvre efficacement une stratégie SEO locale fondée sur l’inventaire local, plusieurs types d’outils peuvent vous aider :</p> <ul> <li><strong>Outils de gestion de fiches locales</strong> : solutions permettant de gérer en masse les fiches Google Business Profile, d’uniformiser les informations (NAP : Name, Address, Phone), de suivre les avis et de publier des posts locaux.</li> <li><strong>Outils d’audit SEO</strong> : analyse technique du site, des balises, des performances mobiles, des données structurées et des problèmes d’indexation, avec un focus possible sur les pages locales.</li> <li><strong>Outils de suivi de positions locales</strong> : suivi des classements pour des mots-clés spécifiques dans des zones géographiques données (ville, code postal, quartier).</li> <li><strong>Outils de gestion d’avis</strong> : centralisation des avis issus de multiples plateformes, suivi de la réputation locale, automatisation de la collecte d’avis après achat ou visite.</li> <li><strong>Plateformes d’analyse de trafic</strong> : mesure des performances de vos pages magasins, des pages produits locales et du trafic provenant des recherches locales.</li> </ul> <h3 id="outils-pour-l-inventaire-local">Outils pour l’inventaire local</h3> <p>Pour la partie inventaire local à proprement parler, les briques suivantes sont généralement nécessaires :</p> <ul> <li><strong>ERP ou système de gestion des stocks</strong> : solution centralisant les informations de stock par magasin et par produit.</li> <li><strong>Connecteurs e-commerce</strong> : modules ou développements spécifiques pour relier votre ERP ou système de caisse à votre site e-commerce.</li> <li><strong>Google Merchant Center</strong> : plateforme permettant de transmettre vos flux produits et vos flux d’inventaire local à Google.</li> <li><strong>API d’inventaire</strong> : interface permettant une <strong>mise à jour automatisée</strong> de la disponibilité des produits par magasin.</li> <li><strong>Outils de monitoring</strong> : contrôle de la qualité des flux (erreurs, données manquantes, incohérences de prix ou de stock) et suivi des performances des annonces et des résultats enrichis.</li> </ul> <p>Le choix des outils dépendra de votre taille, de la complexité de votre réseau de magasins, de votre stack technique existant et de vos objectifs de croissance.</p> <h2 id="strategie-priorisation-et-mise-en-uvre">Stratégie, priorisation et mise en œuvre</h2> <h3 id="prioriser-les-actions-a-fort-impact">Prioriser les actions à fort impact</h3> <p>Pour tirer le maximum de valeur du SEO local et de l’inventaire local, il est utile de prioriser les actions en fonction de leur impact potentiel et de la complexité de mise en œuvre :</p> <ul> <li><strong>Étape 1 : fondations locales</strong> – création ou optimisation de vos fiches Google Business Profile, mise à jour des NAP, gestion des horaires et des photos, réponse systématique aux avis.</li> <li><strong>Étape 2 : pages magasins</strong> – création ou refonte de pages dédiées à chaque point de vente, avec un contenu unique, optimisé et structuré.</li> <li><strong>Étape 3 : contenu et avis</strong> – production de contenus locaux (guides, actualités, promotions) et mise en place d’un dispositif de collecte d’avis clients.</li> <li><strong>Étape 4 : inventaire local</strong> – mise en place de la synchronisation de votre inventaire par magasin avec votre site et Google Merchant Center.</li> <li><strong>Étape 5 : automatisation et optimisation continue</strong> – amélioration du balisage, optimisation des flux, ajustement des contenus en fonction des performances et des retours clients.</li> </ul> <h3 id="mesurer-la-performance">Mesurer la performance</h3> <p>Une stratégie de SEO local et d’inventaire local doit s’accompagner d’indicateurs de performance précis. Parmi les KPI à suivre :</p> <ul> <li>trafic organique sur les <strong>pages magasins</strong> et les <strong>pages produits locales</strong> ;</li> <li>positions moyennes sur des <strong>requêtes locales</strong> ciblées ;</li> <li>clics et actions issus de Google Business Profile (appels, demandes d’itinéraire, clics vers le site) ;</li> <li>taux de conversion des visites locales en <strong>achats</strong> ou en <strong>leads</strong> ;</li> <li>impact de l’intégration de l’inventaire local sur le volume de visites en magasin ;</li> <li>évolution de la <strong>note moyenne</strong> et du volume d’avis clients.</li> </ul> <p>En croisant les données de vos outils d’analytics, de vos systèmes de caisse et de Google, vous pouvez mesurer concrètement la contribution du SEO local et de l’inventaire local à votre chiffre d’affaires.</p> <h2 id="conclusion-operationnelle">Conclusion opérationnelle</h2> <p>Le <strong>SEO local</strong> et l’<strong>inventaire local</strong> ne sont plus des sujets réservés aux grands acteurs du retail. Toute entreprise disposant d’une présence physique et d’une offre de produits ou de services localisée peut en tirer parti, à condition de :</p> <ul> <li>poser des <strong>fondations solides</strong> (fiches Google Business Profile, pages magasins, données structurées) ;</li> <li>investir dans une <strong>gestion rigoureuse de l’inventaire</strong> et de sa mise à jour ;</li> <li>produire un <strong>contenu de qualité</strong>, utile et spécifiquement orienté vers les besoins des clients locaux ;</li> <li>adopter une démarche <strong>omni­canale</strong> cohérente, connectant la recherche en ligne à l’expérience en magasin.</li> </ul> <p>En combinant ces éléments, vous transformez les recherches locales en <strong>visites qualifiées</strong>, puis en <strong>ventes</strong>, tout en offrant une meilleure expérience à vos clients. L’inventaire local devient alors bien plus qu’un simple jeu de données : un levier stratégique au service de votre croissance.</p> </body> </html> </div> <!-- CTA Section --> <div class="mt-12 pt-8 border-t border-gray-200"> <div class="bg-gradient-to-r from-purple-600 to-blue-600 rounded-xl p-8 text-center text-white"> <h3 class="text-2xl font-bold mb-4">Besoin d'aide avec votre SEO ?</h3> <p class="mb-6 text-purple-100">Notre équipe d'experts peut vous aider à optimiser votre site e-commerce</p> <div class="flex flex-col sm:flex-row gap-4 justify-center"> <a href="/seo-ecommerce" class="bg-white text-purple-600 px-8 py-3 rounded-lg font-semibold hover:bg-purple-50 transition inline-block"> Découvrir nos services SEO </a> <a href="/#contact" class="bg-purple-800 text-white px-8 py-3 rounded-lg font-semibold hover:bg-purple-900 transition inline-block"> Nous contacter </a> </div> </div> </div> </article> <!-- Section Commentaires --> <div class="mt-12 max-w-4xl mx-auto bg-white rounded-2xl shadow-xl p-8 md:p-12"> <h2 class="text-3xl font-bold text-gray-900 mb-6 flex items-center gap-3"> <i class="fas fa-comments text-purple-600"></i> Commentaires </h2> <!-- Liste des commentaires approuvés --> <div id="comments-list" class="mb-8 space-y-6"> <!-- Les commentaires approuvés seront chargés ici via JavaScript --> </div> <!-- Formulaire de commentaire --> <div class="border-t border-gray-200 pt-8"> <h3 class="text-xl font-semibold text-gray-900 mb-4">Laisser un commentaire</h3> <form id="comment-form" class="space-y-4"> <input type="hidden" id="article-slug" value="seo-local-local-inventory-et-inventaire-local-le-guide-complet"> <div class="grid md:grid-cols-2 gap-4"> <div> <label for="comment-name" class="block text-sm font-medium text-gray-700 mb-2">Nom *</label> <input type="text" id="comment-name" name="name" required class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-600 focus:border-transparent"> </div> <div> <label for="comment-email" class="block text-sm font-medium text-gray-700 mb-2">Email *</label> <input type="email" id="comment-email" name="email" required class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-600 focus:border-transparent"> </div> </div> <div> <label for="comment-message" class="block text-sm font-medium text-gray-700 mb-2">Message *</label> <textarea id="comment-message" name="message" rows="5" required class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-600 focus:border-transparent"></textarea> </div> <div class="text-sm text-gray-600"> <i class="fas fa-info-circle text-purple-600"></i> Votre commentaire sera soumis à modération avant publication. </div> <button type="submit" class="bg-gradient-to-r from-purple-600 to-blue-600 text-white px-8 py-3 rounded-lg font-semibold hover:from-purple-700 hover:to-blue-700 transition inline-flex items-center gap-2"> <i class="fas fa-paper-plane"></i> Publier le commentaire </button> </form> <div id="comment-status" class="mt-4 hidden"></div> </div> </div> <!-- Navigation Articles --> <div class="mt-12 max-w-4xl mx-auto"> <a href="/blog" class="inline-flex items-center gap-2 text-purple-600 hover:text-purple-800 transition font-semibold"> <i class="fas fa-arrow-left"></i> Retour au blog </a> </div> </div> </section> <!-- Script pour les commentaires --> <script> const articleSlug = 'seo-local-local-inventory-et-inventaire-local-le-guide-complet'; // Charger les commentaires approuvés async function loadComments() { try { const response = await fetch(`/gestion-commentaires/get-comments.php?slug=${articleSlug}`); const data = await response.json(); const commentsList = document.getElementById('comments-list'); if (data.success && data.comments.length > 0) { commentsList.innerHTML = data.comments.map(comment => ` <div class="border-l-4 border-purple-600 pl-4 py-2"> <div class="flex items-center gap-2 mb-2"> <strong class="text-gray-900">${comment.name}</strong> <span class="text-sm text-gray-500">•</span> <span class="text-sm text-gray-500">${new Date(comment.date).toLocaleDateString('fr-FR')}</span> </div> <p class="text-gray-700">${comment.message}</p> </div> `).join(''); } else { commentsList.innerHTML = '<p class="text-gray-500 italic">Aucun commentaire pour le moment. Soyez le premier à commenter !</p>'; } } catch (error) { console.error('Erreur lors du chargement des commentaires:', error); } } // Gérer la soumission du formulaire document.getElementById('comment-form').addEventListener('submit', async function(e) { e.preventDefault(); const formData = { slug: articleSlug, name: document.getElementById('comment-name').value, email: document.getElementById('comment-email').value, message: document.getElementById('comment-message').value }; const submitBtn = this.querySelector('button[type="submit"]'); const originalText = submitBtn.innerHTML; submitBtn.disabled = true; submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Envoi en cours...'; try { const response = await fetch('/gestion-commentaires/submit-comment.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData) }); const result = await response.json(); const messageDiv = document.getElementById('comment-status'); if (result.success) { messageDiv.className = 'mt-4 p-4 bg-green-100 border border-green-400 text-green-700 rounded-lg'; messageDiv.textContent = 'Merci ! Votre commentaire a été soumis et sera publié après modération.'; messageDiv.classList.remove('hidden'); this.reset(); } else { messageDiv.className = 'mt-4 p-4 bg-red-100 border border-red-400 text-red-700 rounded-lg'; messageDiv.textContent = result.message || 'Une erreur est survenue. Veuillez réessayer.'; messageDiv.classList.remove('hidden'); } } catch (error) { const messageDiv = document.getElementById('comment-status'); messageDiv.className = 'mt-4 p-4 bg-red-100 border border-red-400 text-red-700 rounded-lg'; messageDiv.textContent = 'Erreur de connexion. Veuillez réessayer plus tard.'; messageDiv.classList.remove('hidden'); } finally { submitBtn.disabled = false; submitBtn.innerHTML = originalText; } }); // Charger les commentaires au chargement de la page loadComments(); </script> <footer class="bg-gray-950 text-gray-400 py-12"> <div class="container mx-auto px-6"> <div class="flex flex-col md:flex-row justify-between items-center"> <div class="mb-6 md:mb-0 flex items-start gap-4"> <img src="/images/logo.png" alt="Logo VRAIVEX" class="h-32 w-32 md:h-40 md:w-40 object-contain"> <div> <a href="#" class="text-2xl font-bold block"> <span class="gradient-text">VRAIVEX</span> </a> <p class="mt-2 text-sm">Automatisation, IA et SEO au service de la performance e-commerce</p> </div> </div> <div class="flex flex-col items-center md:items-end"> <div class="grid grid-cols-2 md:flex md:flex-wrap gap-3 md:space-x-6 mb-4 text-center md:text-right"> <a href="/#about" class="hover:text-white transition text-sm">À propos</a> <a href="/#services" class="hover:text-white transition text-sm">Services</a> <a href="/#prestations" class="hover:text-white transition text-sm">Prestations</a> <a href="/#bestsellers" class="hover:text-white transition text-sm">Best Sellers</a> <a href="/#brands" class="hover:text-white transition text-sm">Nos marques</a> <a href="/creation-site-ecommerce" class="hover:text-white transition text-sm">Création Sites</a> <a href="/seo-ecommerce" class="hover:text-white transition text-sm">SEO E-commerce</a> <a href="/partenaires" class="hover:text-white transition text-sm">Partenaires</a> <a href="/#contact" class="hover:text-white transition text-sm">Contact</a> </div> <p class="text-sm text-center md:text-right">© 2025 VRAIVEX. Tous droits réservés.</p> </div> </div> </div> </footer> <!-- Back to Top Button --> <button id="backToTop" class="fixed bottom-8 right-8 bg-gradient-to-r from-purple-600 to-blue-600 text-white p-4 rounded-full shadow-lg hover:shadow-xl transform hover:scale-110 transition-all duration-300 z-50 hidden"> <i class="fas fa-arrow-up text-xl"></i> </button> <script> // Header scroll effect window.addEventListener('scroll', function() { const header = document.getElementById('header'); if (window.scrollY > 100) { header.classList.add('header-scrolled'); } else { header.classList.remove('header-scrolled'); } }); // Smooth scrolling for anchor links document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function (e) { e.preventDefault(); document.querySelector(this.getAttribute('href')).scrollIntoView({ behavior: 'smooth' }); }); }); // Mobile menu toggle const mobileMenuButton = document.getElementById('mobileMenuButton'); const mobileMenu = document.getElementById('mobileMenu'); const menuIcon = document.getElementById('menuIcon'); if (mobileMenuButton && mobileMenu) { mobileMenuButton.addEventListener('click', function() { mobileMenu.classList.toggle('hidden'); // Toggle icon between bars and times if (mobileMenu.classList.contains('hidden')) { menuIcon.classList.remove('fa-times'); menuIcon.classList.add('fa-bars'); } else { menuIcon.classList.remove('fa-bars'); menuIcon.classList.add('fa-times'); } }); // Close menu when clicking on a link const mobileLinks = mobileMenu.querySelectorAll('a'); mobileLinks.forEach(link => { link.addEventListener('click', function() { mobileMenu.classList.add('hidden'); menuIcon.classList.remove('fa-times'); menuIcon.classList.add('fa-bars'); }); }); } // Brands Carousel const brandsCarousel = document.getElementById('brandsCarousel'); const brandsContainer = document.getElementById('brandsContainer'); const brandsPrevBtn = document.getElementById('brandsPrevBtn'); const brandsNextBtn = document.getElementById('brandsNextBtn'); const brandsPrevBtnMobile = document.getElementById('brandsPrevBtnMobile'); const brandsNextBtnMobile = document.getElementById('brandsNextBtnMobile'); if (brandsContainer && brandsCarousel) { let currentIndex = 0; const cards = brandsContainer.querySelectorAll('.brand-card'); const cardsPerView = { mobile: 1, tablet: 2, desktop: 3, large: 4 }; function getCardsPerView() { const width = window.innerWidth; if (width >= 1280) return cardsPerView.large; if (width >= 1024) return cardsPerView.desktop; if (width >= 768) return cardsPerView.tablet; return cardsPerView.mobile; } function updateCarousel() { const cardsPerViewCount = getCardsPerView(); const containerWidth = brandsCarousel.offsetWidth; const cardWidth = containerWidth / cardsPerViewCount; const maxIndex = Math.max(0, cards.length - cardsPerViewCount); currentIndex = Math.min(currentIndex, maxIndex); brandsContainer.style.transform = `translateX(-${currentIndex * cardWidth}px)`; // Update button states const isAtStart = currentIndex === 0; const isAtEnd = currentIndex >= maxIndex; if (brandsPrevBtn) { brandsPrevBtn.style.opacity = isAtStart ? '0.5' : '1'; brandsPrevBtn.style.cursor = isAtStart ? 'not-allowed' : 'pointer'; } if (brandsNextBtn) { brandsNextBtn.style.opacity = isAtEnd ? '0.5' : '1'; brandsNextBtn.style.cursor = isAtEnd ? 'not-allowed' : 'pointer'; } if (brandsPrevBtnMobile) { brandsPrevBtnMobile.style.opacity = isAtStart ? '0.5' : '1'; brandsPrevBtnMobile.style.cursor = isAtStart ? 'not-allowed' : 'pointer'; } if (brandsNextBtnMobile) { brandsNextBtnMobile.style.opacity = isAtEnd ? '0.5' : '1'; brandsNextBtnMobile.style.cursor = isAtEnd ? 'not-allowed' : 'pointer'; } } function nextSlide() { const cardsPerViewCount = getCardsPerView(); const maxIndex = Math.max(0, cards.length - cardsPerViewCount); if (currentIndex < maxIndex) { currentIndex++; updateCarousel(); } } function prevSlide() { if (currentIndex > 0) { currentIndex--; updateCarousel(); } } // Event listeners if (brandsNextBtn) brandsNextBtn.addEventListener('click', nextSlide); if (brandsPrevBtn) brandsPrevBtn.addEventListener('click', prevSlide); if (brandsNextBtnMobile) brandsNextBtnMobile.addEventListener('click', nextSlide); if (brandsPrevBtnMobile) brandsPrevBtnMobile.addEventListener('click', prevSlide); // Set responsive width for cards function setCardWidths() { const cardsPerViewCount = getCardsPerView(); const containerWidth = brandsCarousel.offsetWidth; const gap = 24; // 24px gap const cardWidth = (containerWidth - (gap * (cardsPerViewCount - 1))) / cardsPerViewCount; cards.forEach(card => { card.style.width = `${cardWidth}px`; card.style.flexShrink = '0'; }); } // Initialize setCardWidths(); updateCarousel(); // Update on resize let resizeTimeout; window.addEventListener('resize', function() { clearTimeout(resizeTimeout); resizeTimeout = setTimeout(function() { setCardWidths(); currentIndex = 0; updateCarousel(); }, 250); }); } </script> <script> // Header scroll effect window.addEventListener('scroll', function() { const header = document.getElementById('header'); if (window.scrollY > 50) { header.classList.add('header-scrolled'); } else { header.classList.remove('header-scrolled'); } }); // Scroll animations const observerOptions = { threshold: 0.1, rootMargin: '0px 0px -50px 0px' }; const observer = new IntersectionObserver(function(entries) { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('visible'); } }); }, observerOptions); // Observe all fade-in-up elements document.querySelectorAll('.fade-in-up').forEach(el => { observer.observe(el); }); // Smooth scroll for anchor links document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function (e) { e.preventDefault(); const target = document.querySelector(this.getAttribute('href')); if (target) { target.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }); }); // Counter animation for stats function animateCounter(element, target, duration = 2000) { let start = 0; const increment = target / (duration / 16); const timer = setInterval(() => { start += increment; if (start >= target) { element.textContent = target + (element.textContent.includes('+') ? '+' : '') + (element.textContent.includes('K') ? 'K€' : ''); clearInterval(timer); } else { element.textContent = Math.floor(start) + (element.textContent.includes('+') ? '+' : '') + (element.textContent.includes('K') ? 'K€' : ''); } }, 16); } // Observe stats section const statsObserver = new IntersectionObserver(function(entries) { entries.forEach(entry => { if (entry.isIntersecting && !entry.target.classList.contains('animated')) { entry.target.classList.add('animated'); const statsCards = entry.target.querySelectorAll('.stats-card'); statsCards.forEach((card, index) => { setTimeout(() => { card.style.opacity = '0'; card.style.transform = 'translateY(20px)'; setTimeout(() => { card.style.transition = 'all 0.6s ease'; card.style.opacity = '1'; card.style.transform = 'translateY(0)'; }, 100); }, index * 100); }); } }); }, { threshold: 0.3 }); const statsSection = document.querySelector('section.bg-gradient-to-r'); if (statsSection) { statsObserver.observe(statsSection); } </script> <script> // Gestion du formulaire de contact const contactForm = document.getElementById('contact-form'); const formMessage = document.getElementById('form-message'); const submitBtn = document.getElementById('submit-btn'); if (contactForm) { contactForm.addEventListener('submit', async function(e) { e.preventDefault(); // Désactiver le bouton pendant l'envoi submitBtn.disabled = true; submitBtn.textContent = 'Envoi en cours...'; // Récupérer les données du formulaire const formData = new FormData(contactForm); try { // Vérifier que les données sont bien dans le FormData const formDataObj = { name: formData.get('name'), email: formData.get('email'), subject: formData.get('subject'), message: formData.get('message') }; console.log('Données du formulaire:', formDataObj); // Vérifier que tous les champs sont remplis if (!formDataObj.name || !formDataObj.email || !formDataObj.subject || !formDataObj.message) { formMessage.classList.remove('hidden'); formMessage.className = 'mb-6 p-4 rounded-lg bg-red-600 text-white'; formMessage.textContent = 'Veuillez remplir tous les champs du formulaire.'; submitBtn.disabled = false; submitBtn.textContent = 'Envoyer le message'; return; } // Essayer d'abord avec JSON (plus fiable) // Si ça ne fonctionne pas, on essaiera avec FormData let response; try { // Méthode 1 : Envoyer en JSON (plus fiable selon les forums) response = await fetch('gestion-formulaire-contact/send-email-json.php', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(formDataObj) }); } catch (jsonError) { console.warn('Erreur avec JSON, essai avec FormData:', jsonError); // Méthode 2 : Fallback avec FormData response = await fetch('send-email.php', { method: 'POST', body: formData }); } // Lire le texte de la réponse d'abord pour déboguer const responseText = await response.text(); console.log('Réponse serveur:', responseText.substring(0, 500)); // Vérifier si la réponse est OK if (!response.ok) { // Essayer de parser le JSON d'erreur try { const errorResult = JSON.parse(responseText); // Afficher le message d'erreur du serveur directement à l'utilisateur formMessage.classList.remove('hidden'); formMessage.className = 'mb-6 p-4 rounded-lg bg-red-600 text-white'; formMessage.textContent = errorResult.message || `Erreur ${response.status}: ${response.statusText}`; submitBtn.disabled = false; submitBtn.textContent = 'Envoyer le message'; return; // Sortir de la fonction pour ne pas continuer } catch (e) { throw new Error(`Erreur HTTP ${response.status}: ${response.statusText}. Réponse: ${responseText.substring(0, 200)}`); } } // Vérifier si PHP n'est pas exécuté (le serveur renvoie le code PHP brut) if (responseText.trim().startsWith('<?php') || responseText.includes('<?php')) { console.error('ERREUR: PHP n\'est pas exécuté par le serveur. Le code PHP est renvoyé brut.'); console.error('Le serveur web n\'est pas configuré pour exécuter PHP.'); // Utiliser la solution de secours : sauvegarder dans localStorage const messageData = { name: formData.get('name'), email: formData.get('email'), subject: formData.get('subject'), message: formData.get('message'), timestamp: new Date().toISOString() }; // Sauvegarder dans localStorage comme solution de secours const savedMessages = JSON.parse(localStorage.getItem('vraivex_messages') || '[]'); savedMessages.push(messageData); localStorage.setItem('vraivex_messages', JSON.stringify(savedMessages)); // Afficher un message spécial formMessage.classList.remove('hidden'); formMessage.className = 'mb-6 p-4 rounded-lg bg-yellow-600 text-white'; formMessage.innerHTML = '⚠️ PHP n\'est pas configuré sur le serveur. Votre message a été sauvegardé localement. <br>Veuillez nous contacter directement à <strong>contact@vraivex.fr</strong> ou consulter les messages sauvegardés dans la console du navigateur.'; // Afficher les messages sauvegardés dans la console console.log('Messages sauvegardés localement:', savedMessages); console.log('Pour consulter les messages, tapez dans la console: JSON.parse(localStorage.getItem("vraivex_messages"))'); return; // Sortir de la fonction } // Essayer de parser le JSON let result; try { result = JSON.parse(responseText); } catch (parseError) { console.error('Erreur de parsing JSON:', parseError); console.error('Réponse reçue:', responseText.substring(0, 500)); throw new Error('Le serveur a renvoyé une réponse invalide. Vérifiez la console pour plus de détails.'); } // Afficher le message de résultat formMessage.classList.remove('hidden'); if (result.success) { formMessage.className = 'mb-6 p-4 rounded-lg bg-green-600 text-white'; formMessage.textContent = 'Message envoyé avec succès ! Nous vous répondrons dans les plus brefs délais.'; contactForm.reset(); } else { formMessage.className = 'mb-6 p-4 rounded-lg bg-red-600 text-white'; let errorMsg = result.message || 'Une erreur est survenue. Veuillez réessayer.'; // Afficher le message de debug en développement (à retirer en production) if (result.debug) { console.error('Erreur détaillée:', result.debug); } formMessage.textContent = errorMsg; } } catch (error) { formMessage.classList.remove('hidden'); formMessage.className = 'mb-6 p-4 rounded-lg bg-red-600 text-white'; // Message d'erreur plus détaillé pour le débogage let errorMsg = 'Une erreur est survenue lors de la communication avec le serveur. '; errorMsg += 'Veuillez réessayer plus tard ou nous contacter directement à contact@vraivex.fr'; // En mode développement, afficher plus de détails if (error.message) { console.error('Erreur détaillée:', error); console.error('Message:', error.message); console.error('Stack:', error.stack); } formMessage.textContent = errorMsg; } finally { // Réactiver le bouton submitBtn.disabled = false; submitBtn.textContent = 'Envoyer le message'; // Masquer le message après 5 secondes setTimeout(() => { formMessage.classList.add('hidden'); }, 5000); } }); } // Back to Top Button functionality const backToTopButton = document.getElementById('backToTop'); // Show/hide button based on scroll position window.addEventListener('scroll', () => { if (window.pageYOffset > 300) { backToTopButton.classList.remove('hidden'); } else { backToTopButton.classList.add('hidden'); } }); // Smooth scroll to top when clicked backToTopButton.addEventListener('click', () => { window.scrollTo({ top: 0, behavior: 'smooth' }); }); </script> </body> </html>