Article SEO SEO Technique

Balise canonique (canonical tag) : guide complet On-Page SEO

Sommaire de l'article

Introduction

La balise canonique (ou canonical tag) est un élément central du SEO on-page. Cet élément HTML aide les moteurs de recherche à comprendre quelle version d’un contenu doit être considérée comme la version principale lorsqu’il existe plusieurs pages similaires ou identiques. En l’utilisant correctement, vous limitez les problèmes de contenu dupliqué, consolidez vos signaux SEO et améliorez la visibilité de vos pages stratégiques.

Concrètement, la balise canonique permet d’indiquer une URL canonique, c’est-à-dire l’URL de référence à indexer et à classer prioritairement dans les résultats de recherche. Elle joue un rôle essentiel pour éviter la dilution de l’autorité entre plusieurs variantes d’une même page, optimiser le budget de crawl et clarifier la structure de votre site pour les moteurs de recherche.

Dans ce guide complet, vous découvrirez en détail le fonctionnement de la balise canonique, les cas d’usage concrets, les bonnes pratiques techniques et stratégiques, les erreurs fréquentes à éviter, ainsi que les principaux outils qui facilitent sa mise en œuvre et son contrôle.

Concepts clés

Qu’est-ce qu’une balise canonique ?

La balise canonique est une balise HTML du type insérée dans la section d’une page Web. Elle prend la forme suivante :


Cette balise indique aux moteurs de recherche quelle URL doit être traitée comme la version principale d’un ensemble de pages au contenu identique ou très similaire. L’URL indiquée dans l’attribut href est appelée URL canonique.

Une URL canonique est donc la version préférée d’une page Web lorsque plusieurs URLs affichent un contenu proche. Les robots de moteurs tels que Google, Bing ou autres l’utilisent pour concentrer l’indexation et les signaux SEO sur une seule page de référence.

Dans la pratique, la balise canonique est une recommandation adressée aux moteurs de recherche. Elle leur signale l’URL à privilégier, mais ils conservent la liberté de choisir une autre URL comme canonique s’ils estiment que celle-ci est plus cohérente (par exemple en se basant sur les liens, les redirections, les sitemaps ou d’autres signaux).

Objectif principal de la balise canonique

L’objectif principal de la balise canonique est de gérer les contenus dupliqués ou quasi dupliqués au sein d’un site ou entre plusieurs domaines. Sans URL canonique bien définie, plusieurs variantes d’une même page peuvent :

  • se faire concurrence entre elles dans les résultats de recherche ;
  • fragmenter les signaux d’autorité (backlinks, comportement utilisateur, etc.) ;
  • consommer inutilement le budget de crawl ;
  • rendre plus difficile la compréhension de la structure du site par les moteurs.

En indiquant clairement une page de référence, la balise canonique permet de centraliser l’autorité SEO, d’améliorer la pertinence des résultats affichés et de renforcer la cohérence globale du site.

Comment fonctionne la balise canonique ?

Lorsqu’un moteur de recherche analyse une page, il prend en compte de nombreux signaux pour décider :

  • quelle version d’un contenu sera indexée ;
  • quelle URL sera affichée dans les résultats de recherche ;
  • comment répartir l’autorité et les signaux (liens, interactions, etc.).

Parmi ces signaux, la balise rel="canonical" joue un rôle important. Lorsque cette balise est présente dans le d’une page, le moteur comprend que :

  • la page actuelle fait partie d’un ensemble de pages équivalentes ou très proches ;
  • l’URL indiquée dans la balise est celle que le site souhaite voir indexée comme référence ;
  • les autres variantes doivent être traitées comme des versions secondaires.

Le moteur de recherche peut alors décider de :

  • considérer l’URL canonique comme la version principale dans l’index ;
  • regrouper les signaux (par exemple les liens entrants) des pages dupliquées vers cette URL canonique ;
  • afficher de préférence l’URL canonique dans les pages de résultats (SERP).

Il est important de comprendre que la balise canonique n’est pas une redirection : les utilisateurs peuvent toujours accéder aux pages secondaires, qui restent disponibles et consultables. La balise agit uniquement au niveau des moteurs de recherche.

Où placer la balise canonique ?

Pour être prise en compte correctement, la balise canonique doit respecter quelques règles techniques :

  • être placée dans la section du code HTML de la page ;
  • être unique par page (une seule balise canonique par document) ;
  • pointer vers une URL accessible, indexable et cohérente avec le contenu de la page ;
  • utiliser une URL complète (absolue) incluant le protocole (http/https) et le nom de domaine.

Auto-référencement (self canonical)

Une pratique courante en SEO consiste à utiliser une balise canonique auto-référente. Dans ce cas, l’URL indiquée dans la balise est exactement la même que l’URL de la page.

Exemple :


Cette approche présente plusieurs avantages :

  • elle clarifie pour les moteurs de recherche quelle version exacte (avec ou sans slash final, avec ou sans www, protocole https, etc.) doit être considérée comme la référence ;
  • elle limite les problèmes de duplication liés à l’ajout de paramètres d’URL (par exemple UTM, filtres, trier par, pagination légère, etc.) ;
  • elle participe à la cohérence globale de la stratégie de canonicalisation du site.

Exemples concrets d’utilisation de la balise canonique

1. Variations d’URL avec des paramètres

Les paramètres d’URL peuvent générer de nombreuses versions d’une même page : filtrage, tri, suivi de campagnes, pagination, sessions, etc. Par exemple :

  • https://www.exemple.com/chaussures-femme/
  • https://www.exemple.com/chaussures-femme/?tri=prix
  • https://www.exemple.com/chaussures-femme/?utm_source=newsletter

Dans ce cas, il est généralement pertinent de définir comme URL canonique la version sans paramètre, qui représente la page principale de la catégorie :


2. Gestion de versions mobile et desktop

Sur les anciens dispositifs avec séparation entre m.exemple.com et www.exemple.com, il pouvait exister une version mobile et une version desktop distinctes d’une même page. La balise canonique était alors souvent utilisée pour indiquer la version desktop comme référence, tandis que des balises spécifiques signalaient la version mobile.

Aujourd’hui, la très grande majorité des sites utilisent le responsive design avec une seule URL pour toutes les tailles d’écran. Dans ce contexte, la balise canonique sert surtout à clarifier la version préférée de l’URL (https, sans paramètre, etc.), plutôt qu’à faire la distinction entre mobile et desktop.

3. Pages de filtrage et tri en e-commerce

Les sites e-commerce sont particulièrement exposés au duplicate content en raison des multiples combinaisons de filtres et de tris :

  • filtre par taille, couleur, prix ;
  • tri par popularité, nouveauté, prix croissant ou décroissant ;
  • combinaisons avancées de paramètres.

Par exemple :

  • https://www.exemple.com/robes/
  • https://www.exemple.com/robes/?couleur=rouge
  • https://www.exemple.com/robes/?couleur=rouge&taille=m&tri=prix

Dans de nombreux cas, il est recommandé de définir l’URL principale de la catégorie (sans paramètre) comme URL canonique afin de :

  • centraliser l’autorité sur la page de catégorie ;
  • éviter la multiplication de pages presque identiques dans l’index ;
  • améliorer la pertinence globale du site pour les requêtes génériques.

4. Pages imprimables, versions AMP ou pages de test

Sur certains sites, une même page peut exister en plusieurs versions :

  • version classique ;
  • version imprimable ;
  • version AMP ou autre format spécifique ;
  • versions de test ou de préproduction accessibles par erreur.

La balise canonique permet dans ces cas de signaler aux moteurs que la version principale à indexer est par exemple la page HTML classique. Les autres versions restent accessibles à l’utilisateur mais ne sont pas destinées à se positionner dans les résultats de recherche.

5. Contenu disponible sur plusieurs URLs

Il peut arriver qu’un même contenu soit accessible via différentes URLs, par exemple :

  • avec et sans slash final (/page et /page/) ;
  • avec et sans www ;
  • en http et en https ;
  • via plusieurs chemins internes différents.

Dans une stratégie SEO cohérente, on choisit une version unique et stable comme URL canonique (généralement en https, sans duplication de chemin) et l’on utilise cette URL partout : dans la balise canonique, les liens internes, les sitemaps et les redirections.

Bonnes pratiques pour optimiser la balise canonique

1. Optimiser le contenu de la page canonique

La balise canonique sera réellement efficace si la page désignée comme canonique propose un contenu unique, riche et de haute qualité. Il est conseillé de :

  • construire une page qui répond de manière complète à l’intention de recherche de l’utilisateur ;
  • optimiser les balises </code> et <code><meta description></code> pour la requête ciblée ;</li> <li>structurer le texte avec des titres hiérarchisés (<code><h1></code>, <code><h2 id="etc-integrer-des-medias-pertinents-images-optimisees-schemas-videos-etc-lorsque-cela-a-du-sens-propo"></code>, <code><h3></code>, etc.) ;</li> <li>intégrer des médias pertinents (images optimisées, schémas, vidéos, etc.) lorsque cela a du sens ;</li> <li>proposer une expérience utilisateur fluide (navigation, temps de chargement, compatibilité mobile).</li> </ul> <p>La balise canonique ne corrige pas un contenu faible : elle sert à <strong>indiquer la meilleure version</strong> d’un ensemble de contenus similaires. Il est donc essentiel que cette version soit réellement la plus complète et la plus pertinente.</p> <h3>2. Améliorer la structure du site</h2> <p>Une <strong>architecture de site claire et logique</strong> limite naturellement les risques de contenus dupliqués. Pour cela :</p> <ul> <li>définissez une structure en catégories et sous-catégories cohérente ;</li> <li>évitez de multiplier les chemins d’accès différents vers un même contenu sans raison ;</li> <li>standardisez la forme des URLs (choix du slash final, format des paramètres, etc.) ;</li> <li>mettez en place des redirections 301 lorsqu’une page est définitivement déplacée ou fusionnée.</li> </ul> <p>La balise canonique vient en complément d’une bonne structure : elle ne doit pas servir à masquer des problèmes d’architecture, mais à affiner la manière dont les moteurs interprètent vos contenus.</p> <h3 id="3-creer-un-contenu-original-et-eviter-les-doublons-inutiles">3. Créer un contenu original et éviter les doublons inutiles</h3> <p>La meilleure manière de limiter l’utilisation complexe de la balise canonique reste de <strong>produire du contenu original</strong>. Quelques bonnes pratiques :</p> <ul> <li>éviter de créer plusieurs pages pour des variantes très proches d’un même sujet lorsqu’une seule page bien structurée suffit ;</li> <li>ne pas dupliquer des descriptions produits identiques sur des dizaines de pages sans valeur ajoutée ;</li> <li>réécrire ou enrichir les contenus réutilisés (communiqués, fiches fournisseurs, etc.) pour en faire une version unique.</li> </ul> <p>Lorsque vous devez malgré tout conserver plusieurs pages très similaires pour des raisons techniques ou métier, la balise canonique devient un outil indispensable pour préciser aux moteurs leur relation.</p> <h3 id="4-gerer-les-redirections-et-les-canoniques-de-maniere-coherente">4. Gérer les redirections et les canoniques de manière cohérente</h3> <p>La balise canonique <strong>ne remplace pas</strong> une redirection 301. Les deux mécanismes ont des usages complémentaires :</p> <ul> <li><strong>Redirection 301</strong> : utilisée lorsque vous souhaitez que les utilisateurs et les moteurs soient redirigés automatiquement vers une nouvelle URL (fusion de contenus, changement d’URL définitif, suppression de page au profit d’une autre, etc.).</li> <li><strong>Balise canonique</strong> : utilisée lorsque plusieurs versions d’une page doivent rester accessibles à l’utilisateur, mais que vous voulez qu’une seule URL soit considérée comme la version de référence pour l’indexation.</li> </ul> <p>Quelques principes à respecter :</p> <ul> <li>évitez de placer une balise canonique vers une URL différente sur une page déjà redirigée en 301 : privilégiez une seule indication claire ;</li> <li>évitez les <strong>chaînes de canonicals</strong> (une page canonique pointant vers une autre page qui elle-même pointe vers une troisième) ;</li> <li>assurez-vous que la page indiquée comme canonique retourne un code HTTP 200 et non une redirection, une erreur 404 ou 410.</li> </ul> <h3 id="5-utiliser-des-urls-absolues-et-coherentes">5. Utiliser des URLs absolues et cohérentes</h3> <p>Pour éviter les problèmes d’interprétation, il est recommandé d’utiliser des <strong>URLs absolues</strong> dans les balises canoniques :</p> <pre><code> </code></pre> <p>Les bonnes pratiques incluent :</p> <ul> <li>le recours systématique au protocole <strong>https</strong> lorsque le site en dispose ;</li> <li>la cohérence entre la version avec ou sans <code>www</code> (alignement sur le choix global du site) ;</li> <li>l’absence de paramètres inutiles dans l’URL canonique ;</li> <li>la prise en compte de la casse (majuscules/minuscules) selon la configuration du serveur.</li> </ul> <h3 id="6-gerer-les-contenus-multilingues-et-multi-regions">6. Gérer les contenus multilingues et multi-régions</h3> <p>Pour les sites qui ciblent plusieurs langues ou pays, plusieurs combinaisons sont possibles :</p> <ul> <li>un domaine ou sous-domaine par langue/pays ;</li> <li>un répertoire par langue (ex : <code>/fr/</code>, <code>/en/</code>, <code>/es/</code>) ;</li> <li>un mélange de ces approches.</li> </ul> <p>Dans ce contexte :</p> <ul> <li>chaque version linguistique doit généralement avoir sa propre <strong>URL canonique</strong> auto-référente ;</li> <li>il est possible de combiner la balise canonique avec des balises <code>hreflang</code> pour indiquer les équivalences linguistiques entre pages ;</li> <li>la balise canonique ne doit pas pointer d’une langue vers une autre si les contenus sont destinés à des publics différents (par exemple ne pas faire pointer la version française vers la version anglaise).</li> </ul> <h3 id="7-verifier-la-coherence-interne-des-canoniques">7. Vérifier la cohérence interne des canoniques</h3> <p>Pour une stratégie de balise canonique efficace, la cohérence est primordiale. Il est recommandé de vérifier régulièrement que :</p> <ul> <li>toutes les pages importantes disposent bien d’une balise canonique ;</li> <li>aucune page ne pointe vers une URL canonique bloquée par un <code>noindex</code> ou un fichier <code>robots.txt</code> ;</li> <li>les canoniques ne forment pas de boucles (A → B, B → A) ;</li> <li>les sitemaps, les liens internes et les balises canoniques désignent tous la même version comme préférée.</li> </ul> <h2 id="erreurs-frequentes-a-eviter">Erreurs fréquentes à éviter</h2> <ul> <li><strong>Pointer vers une page non indexable</strong> : ne canonisez pas une URL bloquée par <code>noindex</code>, mot-clé disallow dans <code>robots.txt</code> ou renvoyant une erreur. L’URL canonique doit être indexable et accessible.</li> <li><strong>Multiplies balises canoniques sur une même page</strong> : une seule balise doit être présente dans le <code><head></code>. En cas de doublons, les moteurs peuvent ignorer l’ensemble.</li> <li><strong>Canoniques contradictoires</strong> : des pages très différentes ne doivent pas être reliées par canonical, au risque de brouiller les signaux.</li> <li><strong>Utiliser le canonical à la place d’une redirection lorsque le contenu a vraiment changé d’URL</strong> : dans ce cas, une redirection 301 est plus appropriée.</li> <li><strong>Copier systématiquement le même canonical sur toutes les pages</strong> sans lien de similarité réelle : cela peut empêcher l’indexation correcte de nombreuses pages utiles.</li> </ul> <h2 id="comportement-reel-des-moteurs-de-recherche">Comportement réel des moteurs de recherche</h2> <p>Les principaux moteurs de recherche utilisent la balise canonique comme un <strong>signal fort mais non absolu</strong>. Ils peuvent décider d’ignorer le canonical si :</p> <ul> <li>le contenu de la page canonique est très différent de celui des pages qui la déclarent ;</li> <li>d’autres signaux (liens, redirections, sitemaps, historique) sont en conflit avec la balise ;</li> <li>l’URL canonique désigne une page non accessible ou de mauvaise qualité.</li> </ul> <p>Ils combinent la balise canonique avec d’autres méthodes de <strong>canonicalisation</strong>, comme :</p> <ul> <li>le choix d’URL préféré en fonction des liens entrants ;</li> <li>les redirections 301 ;</li> <li>les balises <code>hreflang</code> ;</li> <li>les déclarations d’URL dans les sitemaps ;</li> <li>les en-têtes HTTP <code>Link: <...>; rel="canonical"</code> pour les contenus non HTML.</li> </ul> <h2 id="methodes-alternatives-pour-definir-un-canonical">Méthodes alternatives pour définir un canonical</h2> <p>En plus de la balise HTML <code>link rel="canonical"</code>, il existe d’autres façons de spécifier une URL canonique :</p> <ul> <li><strong>En-tête HTTP :</strong> pour les documents non HTML (PDF, fichiers, etc.), il est possible d’utiliser un en-tête <code>Link</code> pour indiquer l’URL canonique.</li> <li><strong>Sitemaps XML :</strong> en n’incluant que les versions canoniques des pages dans les sitemaps, vous indiquez indirectement aux moteurs quelles URLs sont à privilégier.</li> <li><strong>Liens internes :</strong> en reliant systématiquement vers l’URL canonique (et non vers les variantes), vous renforcez le signal de préférence.</li> </ul> <p>Cependant, pour la plupart des sites, la balise <code>rel="canonical"</code> dans le <code><head></code> du HTML reste la méthode la plus directe et la plus utilisée.</p> <h2 id="outils-et-ressources-pour-gerer-les-balises-canoniques">Outils et ressources pour gérer les balises canoniques</h2> <table> <tr> <th>Outil</th> <th>Description</th> </tr> <tr> <td><strong>Google Search Console</strong></td> <td>Outil gratuit de Google pour analyser les performances d’un site dans les résultats de recherche. Il permet notamment d’identifier les problèmes de contenu dupliqué, de vérifier les URLs effectivement choisies comme canoniques par Google et de comparer ces choix avec les balises déclarées sur le site.</td> </tr> <tr> <td><strong>Google Analytics</strong></td> <td>Solution d’analyse d’audience qui permet de mesurer le trafic sur les différentes pages. Combinée avec une gestion correcte des canoniques, elle aide à comprendre quelle version d’une page concentre réellement les visites issues du référencement naturel.</td> </tr> <tr> <td><strong>Ahrefs</strong></td> <td>Outil SEO complet permettant d’analyser les backlinks, le trafic organique, les performances par page et les problèmes techniques. Il peut détecter les contenus dupliqués, repérer les URLs concurrentes sur un même mot-clé et vérifier la présence de balises canoniques correctes.</td> </tr> <tr> <td><strong>SEMrush</strong></td> <td>Plateforme d’audit SEO et de suivi de positionnement. Son module d’audit technique permet d’identifier les pages sans balise canonique, les canoniques multiples, les boucles et les contradictions entre canoniques, redirections et balises <code>noindex</code>.</td> </tr> <tr> <td><strong>Screaming Frog SEO Spider</strong></td> <td>Logiciel d’exploration de site Web qui reproduit le comportement d’un robot. Il permet de lister rapidement toutes les URLs explorées, de vérifier leur balise canonique, de repérer les erreurs de mise en œuvre et d’exporter des rapports détaillés.</td> </tr> <tr> <td><strong>Extensions de navigateur et outils de développement</strong></td> <td>De nombreuses extensions dédiées au SEO ou les outils de développement intégrés aux navigateurs permettent d’afficher rapidement la balise canonique d’une page, ce qui facilite les vérifications manuelles lors des audits.</td> </tr> </table> <h2 id="checklist-pratique-pour-vos-balises-canoniques">Checklist pratique pour vos balises canoniques</h2> <ul> <li>Chaque page importante dispose-t-elle d’une <strong>balise canonique unique</strong> dans le <code><head></code> ?</li> <li>Les URLs indiquées comme canoniques sont-elles <strong>accessibles, indexables et pertinentes</strong> par rapport au contenu ?</li> <li>Les canoniques utilisent-elles des <strong>URLs complètes en https</strong>, conformes au format choisi pour l’ensemble du site ?</li> <li>Les pages réellement dupliquées ou quasi dupliquées pointent-elles toutes vers la <strong>même URL canonique de référence</strong> ?</li> <li>Les pages de filtrage, tri ou suivi de campagnes utilisent-elles une balise canonique vers la page principale correspondante lorsque nécessaire ?</li> <li>Les redirections 301, les sitemaps XML et les liens internes <strong>confirment-ils le même choix d’URL canonique</strong> ?</li> <li>Les canoniques ne créent-ils ni <strong>boucles</strong>, ni <strong>chaînes</strong>, ni contradictions avec des directives <code>noindex</code> ou des blocages dans <code>robots.txt</code> ?</li> </ul> <h2 id="conclusion-operationnelle">Conclusion opérationnelle</h2> <p>La balise canonique est un <strong>levier technique et stratégique incontournable</strong> du SEO on-page. Bien utilisée, elle permet de contrôler l’indexation de vos contenus, de regrouper les signaux SEO sur vos pages clés et de réduire l’impact des contenus dupliqués. Son efficacité repose à la fois sur une mise en œuvre technique rigoureuse (position, syntaxe, cohérence) et sur une approche éditoriale structurée (contenu unique, architecture claire, gestion maîtrisée des variantes d’URL).</p> <p>En intégrant systématiquement la réflexion sur les URLs canoniques dans vos créations de pages, vos refontes et vos audits réguliers, vous renforcez durablement la performance de votre référencement naturel et la lisibilité de votre site pour les moteurs de recherche comme pour les utilisateurs.</p> </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 id="besoin-d-aide-avec-votre-seo">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> <!-- 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="balise-canonique-canonical-tag-guide-complet-on-page-seo"> <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> <!-- Script pour les commentaires --> <script> const articleSlug = 'balise-canonique-canonical-tag-guide-complet-on-page-seo'; // 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> </article> <!-- 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> <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>