Article SEO SEO Technique

On-page SEO : balise meta charset et encodage des caractères en HTML

Introduction

La balise est un élément HTML fondamental pour garantir l’affichage correct du texte sur une page web et assurer une bonne interprétation du contenu par les navigateurs et les moteurs de recherche. Elle définit l’encodage des caractères utilisé dans le document, ce qui impacte directement le rendu des caractères (lettres accentuées, symboles, emojis, caractères non latins, etc.).

D’un point de vue SEO on-page, la balise meta charset ne constitue pas un facteur de classement direct, mais elle fait partie des prérequis techniques indispensables : sans encodage cohérent, le texte peut devenir illisible pour l’utilisateur comme pour les robots des moteurs de recherche, ce qui peut gêner l’indexation et la compréhension du contenu. Dans cet article, nous allons détailler le fonctionnement de la balise charset, les bonnes pratiques d’implémentation, les erreurs à éviter et son rôle dans une stratégie SEO techniquement saine.

Concepts clés

Qu’est-ce que le meta charset ?

La balise meta charset est un élément de la section d’un document HTML qui permet de spécifier l’encodage des caractères utilisé pour cette page. Elle indique au navigateur comment interpréter les octets du fichier HTML pour les convertir en caractères lisibles.

En HTML5, la forme moderne et recommandée de cette balise est :


Cette syntaxe remplace l’ancienne méthode utilisée dans HTML 4 et XHTML, qui reposait sur l’attribut http-equiv="Content-Type". Aujourd’hui, dans un document HTML5 valide, la balise suivante est suffisante dans la plupart des cas :


   Titre de la page 

    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    

    
    
    
        
    
      ... 

Rôle dans l’encodage des caractères

L’encodage des caractères définit la façon dont chaque caractère (lettre, chiffre, symbole) est représenté en mémoire et transmis sur le réseau. Sans indication explicite, le navigateur peut tenter de deviner l’encodage, ce qui provoque souvent des erreurs : caractères accentués remplacés par des symboles étranges, points d’interrogation, losanges, etc.

L’encodage UTF-8 est aujourd’hui l’encodage dominant sur le web. Il permet de représenter la quasi-totalité des caractères utilisés dans les langues du monde, y compris :

  • les alphabets latins (français, anglais, espagnol, etc.) ;
  • les alphabets non latins (cyrillique, grec, arabe, hébreu, etc.) ;
  • les écritures asiatiques (chinois, japonais, coréen, etc.) ;
  • les symboles techniques, les icônes, les émojis et de nombreux signes typographiques.

Plus de neuf pages web sur dix utilisent aujourd’hui UTF-8, ce qui en fait une norme de facto pour le web moderne. Définir correctement la balise permet donc d’éviter les erreurs d’affichage et d’assurer une expérience utilisateur cohérente, quel que soit le navigateur ou la langue.

Impact sur le SEO

Contrairement à une idée répandue, la balise meta charset n’est pas un signal de classement direct pour les moteurs de recherche. Elle n’améliore pas le positionnement d’une page uniquement par sa présence. En revanche, l’encodage qu’elle définit est essentiel pour que le texte soit lisible et correctement interprété.

L’impact de la balise charset sur le SEO est donc indirect :

  • si l’encodage est erroné ou incohérent, certaines portions de texte peuvent devenir illisibles, ce qui dégrade fortement l’expérience utilisateur ;
  • un contenu mal encodé peut être difficile à analyser pour les robots d’indexation, ce qui peut compliquer la compréhension de la langue, des mots-clés et du contexte ;
  • des problèmes d’encodage récurrents peuvent générer des erreurs techniques, des pages partiellement lisibles ou des contenus dupliqués involontaires, ce qui nuit à la qualité globale du site.

En résumé : la balise meta charset ne fait pas monter un site dans les résultats de recherche par elle-même, mais un mauvais encodage peut avoir des conséquences négatives sur l’indexation, l’accessibilité et la compréhension du contenu. Elle fait donc pleinement partie des fondations techniques du SEO on-page.

Bonnes pratiques pour la balise meta charset

Spécifier le charset dès le début du head

Les navigateurs lisent les fichiers HTML de haut en bas. Pour éviter qu’ils ne tentent d’interpréter le contenu avec un encodage erroné, il est recommandé de placer la balise meta charset aussi tôt que possible dans la section , idéalement juste après la déclaration de type de document.

Un exemple de structure correcte :


   Exemple de page UTF-8   ... 

    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    

    
    
    
        
    
      ... 

Placer la balise charset en tout début de limite fortement les risques d’affichage de caractères « cassés » et permet aux navigateurs de construire le DOM avec les bonnes informations dès le départ.

Utiliser UTF-8 comme encodage par défaut

Dans la très grande majorité des cas, il est recommandé d’utiliser UTF-8 comme encodage par défaut pour toutes les pages HTML. Cet encodage est :

  • universel : il couvre pratiquement toutes les langues modernes ;
  • standardisé : il fait partie de la famille Unicode, utilisée dans de nombreux formats (HTML, XML, JSON, CSV, etc.) ;
  • largement supporté : tous les navigateurs et systèmes modernes gèrent UTF-8 nativement ;
  • recommandé par les principales plateformes et moteurs de recherche.

Une balise correcte en HTML5 doit donc ressembler à :


Il est inutile, dans un document HTML5, d’utiliser la forme longue historique :


Cette syntaxe reste comprise par les navigateurs pour assurer la rétrocompatibilité, mais elle n’est plus nécessaire pour un développement moderne.

Éviter les erreurs courantes

Certaines erreurs reviennent fréquemment lors de la configuration de la balise charset. Voici les plus importantes à éviter :

  • Duplication de la balise : n’ajoutez pas plusieurs balises dans le même document. Seule la première sera prise en compte, ce qui peut créer de la confusion et des comportements inattendus. Limitez-vous à une seule déclaration d’encodage cohérente.
  • Omission du charset : ne comptez pas sur le navigateur pour deviner l’encodage. Spécifier explicitement est une bonne pratique qui évite les erreurs et clarifie le comportement attendu.
  • Incohérence entre le HTML et les fichiers sources : assurez-vous que l’encodage déclaré dans la balise charset correspond bien à celui utilisé par vos fichiers HTML, vos templates, vos feuilles de style intégrant du texte, ainsi que les fichiers générés côté serveur. Une chaîne d’encodage incohérente est l’une des causes les plus fréquentes de caractères illisibles.
  • Conflits avec les en-têtes HTTP : si le serveur envoie un en-tête Content-Type avec un encodage différent de la balise , les navigateurs donnent généralement la priorité à l’en-tête HTTP. Cela peut créer des divergences entre l’encodage attendu et l’encodage effectivement utilisé. Il est donc essentiel de maintenir la cohérence entre configuration serveur et code HTML.
  • Encodages obsolètes ou spécifiques : des encodages comme ISO‑8859‑1 ou Windows‑1252 ont longtemps été utilisés pour les sites occidentaux, mais ils sont aujourd’hui largement supplantés par UTF-8. Les conserver complique la maintenance, limite le support multilingue et augmente le risque d’incompatibilités.

Compatibilité HTML, DOCTYPE et structure de page

Pour bénéficier pleinement des avantages de la balise meta charset en HTML5, il est recommandé d’utiliser une structure de document moderne, avec :

  • un DOCTYPE HTML5 ;
  • un attribut lang sur la balise pour indiquer la langue principale de la page ;
  • la balise au tout début du .

Exemple complet :


   Balise meta charset et encodage UTF-8    

    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    

    
    
    
        
    
      ... 

Cette structure garantit une base propre pour vos pages, et facilite à la fois l’affichage correct des caractères, l’accessibilité, le SEO on-page et la compatibilité avec les navigateurs modernes.

Chaîne d’encodage : interaction entre meta charset et serveur

Rôle des en-têtes HTTP

L’encodage des caractères peut être défini à deux niveaux :

  • dans le document HTML lui-même via  ;
  • dans les en-têtes HTTP envoyés par le serveur, par exemple : Content-Type: text/html; charset=UTF-8.

Lorsqu’un navigateur reçoit à la fois un en-tête HTTP et une balise meta charset, il suit en priorité l’encodage indiqué dans l’en-tête HTTP. En cas de divergence, c’est donc la configuration serveur qui l’emporte le plus souvent. Pour éviter les problèmes, il est crucial d’aligner :

  • la configuration de votre serveur web (Apache, Nginx, IIS, etc.) ;
  • les paramètres d’encodage de votre CMS, de votre framework ou de votre application ;
  • les paramètres d’encodage de vos éditeurs de code et de vos fichiers sources.

Assurer une chaîne UTF‑8 cohérente

Déclarer UTF‑8 dans le HTML ne suffit pas toujours à garantir l’affichage correct du contenu. Pour qu’un site soit pleinement compatible UTF‑8, il est recommandé de vérifier :

  • que tous les fichiers HTML, modèles de pages, fichiers de traduction et scripts contenant du texte sont enregistrés en UTF‑8 ;
  • que la base de données (si elle existe) utilise un jeu de caractères et une collation UTF‑8 adaptés ;
  • que le serveur envoie systématiquement charset=UTF-8 dans les en-têtes HTTP pour les réponses HTML ;
  • que les générateurs de contenu (exports CSV, JSON, flux RSS, etc.) sont également configurés en UTF‑8 si ces fichiers sont consommés par des navigateurs ou des outils tiers.

Une chaîne d’encodage homogène permet non seulement de stabiliser l’affichage, mais aussi de garantir une meilleure compatibilité avec les moteurs de recherche, les navigateurs mobiles, les lecteurs d’écran et les applications externes.

Impact SEO indirect et enjeux d’accessibilité

Encodage et indexation des contenus

Les moteurs de recherche analysent le contenu textuel des pages pour comprendre le sujet traité, identifier les mots-clés, détecter la langue principale et évaluer la pertinence des documents. Un encodage incorrect peut perturber ce processus :

  • certains mots peuvent être tronqués ou altérés par des caractères incompréhensibles ;
  • des blocs entiers de texte peuvent devenir illisibles pour les robots autant que pour les utilisateurs ;
  • la détection de la langue et la compréhension du contexte peuvent être faussées.

Dans un contexte multilingue, un encodage fiable comme UTF‑8 est particulièrement important pour le SEO international : il permet de gérer des langues différentes au sein d’un même site sans multiplier les encodages et sans créer de problèmes d’affichage.

Expérience utilisateur et accessibilité

L’expérience utilisateur est un critère de plus en plus central dans l’optimisation d’un site. Un texte rempli de symboles inattendus ou de caractères cassés réduit la confiance, augmente le taux de rebond et diminue le temps passé sur la page. À l’inverse, un contenu affiché correctement, lisible et bien structuré contribue à de meilleurs signaux comportementaux.

De plus, un encodage cohérent facilite le travail des lecteurs d’écran et des technologies d’assistance, qui ont besoin d’un texte compréhensible pour restituer le contenu aux personnes en situation de handicap. À ce titre, l’usage d’UTF‑8 et la bonne configuration de la balise meta charset participent aussi à l’accessibilité numérique du site.

Différence avec les autres meta tags SEO

La balise meta charset appartient à la famille des meta tags HTML, mais son rôle est purement technique. Elle ne doit pas être confondue avec d’autres balises meta à impact SEO plus direct, telles que :

  • </strong> : balise titre affichée dans l’onglet du navigateur et souvent utilisée comme titre de résultat dans les moteurs de recherche. C’est un signal important pour le classement et le taux de clic (CTR).</li> <li><strong><meta name="description"></strong> : description de la page, souvent reprise comme extrait dans les résultats de recherche. Elle influence le CTR, même si elle n’est pas un facteur de classement direct.</li> <li><strong></strong> : permet de contrôler l’indexation d’une page et le suivi des liens (index/noindex, follow/nofollow, etc.).</li> <li><strong><meta name="viewport"></strong> : essentielle pour le rendu mobile, la réactivité et l’optimisation mobile-first, fortement liée à l’expérience utilisateur sur smartphone et tablette.</li> </ul> <p> Assimiler la balise charset à une « balise SEO » au même titre que <code><title></code> ou <code><meta name="description"></code> n’est donc pas précis. Il s’agit avant tout d’un <strong>paramètre technique d’encodage</strong> qui conditionne la lisibilité du contenu, et qui contribue indirectement à un SEO propre et efficace. </p> <h2 id="outils-pour-verifier-l-encodage-et-la-balise-meta-charset">Outils pour vérifier l’encodage et la balise meta charset</h2> <h3 id="outils-pour-controler-l-encodage-html">Outils pour contrôler l’encodage HTML</h3> <p> Plusieurs outils permettent de vérifier si la balise <strong>meta charset</strong> est correctement configurée et si l’encodage est cohérent sur l’ensemble du site : </p> <ul> <li> <strong>Validator W3C</strong> : le validateur HTML officiel permet de contrôler la conformité de vos pages aux standards du W3C. Il signale notamment les erreurs de déclaration de charset, les DOCTYPE incorrects et les problèmes de structure HTML qui peuvent affecter le rendu. </li> <li> <strong>Extensions de développement pour navigateurs</strong> : des extensions comme « Web Developer » pour Firefox ou Chrome proposent des fonctionnalités pour afficher rapidement les en-têtes HTTP, le code source et les informations d’encodage de la page. </li> <li> <strong>Outils intégrés aux navigateurs</strong> : les outils de développement (DevTools) des navigateurs modernes permettent d’inspecter le code HTML, les en-têtes réseau et les réponses HTTP. Vous pouvez y vérifier le <code>Content-Type</code>, l’encodage et la présence de la balise meta charset. </li> </ul> <h3 id="outils-indispensables-pour-l-optimisation-globale-du-site">Outils indispensables pour l’optimisation globale du site</h3> <p> La balise meta charset s’inscrit dans une démarche plus large d’<strong>optimisation technique</strong> et de <strong>SEO on-page</strong>. D’autres outils sont utiles pour vérifier l’état général du site, détecter les erreurs et améliorer les performances : </p> <ul> <li> <strong>Google Search Console</strong> : permet de surveiller l’indexation, de repérer les erreurs d’exploration, de suivre les performances de recherche et d’identifier des problèmes potentiels liés au contenu ou à la configuration technique. </li> <li> <strong>Google Analytics</strong> : outil d’analyse de trafic qui aide à comprendre le comportement des utilisateurs, les pages qui performent le mieux et les éventuels points de friction. </li> <li> <strong>Screaming Frog SEO Spider</strong> : crawler SEO permettant d’auditer un site en profondeur : balises title et description manquantes ou dupliquées, codes de réponse HTTP, contenu dupliqué, redirections, et également informations relatives aux balises meta. </li> <li> <strong>Validator W3C</strong> : en complément du contrôle de l’encodage, cet outil signale les erreurs de syntaxe HTML ou CSS, ce qui contribue à maintenir un code propre, stable et plus facilement interprétable par les navigateurs. </li> <li> <strong>Outils de mesure de performance</strong> (par exemple des analyseurs de vitesse de page) : ils évaluent la rapidité de chargement, l’optimisation des ressources et le comportement du site sur mobile, des éléments qui complètent la bonne configuration de la balise charset dans une approche globale de qualité technique. </li> <li> <strong>Suites d’outils SEO</strong> (par exemple des plateformes d’audit technique et de suivi de mots-clés) : elles offrent une vision plus large de la santé SEO d’un site, incluant les aspects techniques, de contenu et de popularité. Même si la balise charset n’est qu’un détail dans ces audits, un site bien encodé est plus simple à analyser et à optimiser. </li> </ul> <h2 id="etapes-pratiques-pour-bien-configurer-la-balise-meta-charset">Étapes pratiques pour bien configurer la balise meta charset</h2> <h3 id="1-choisir-l-encodage-approprie">1. Choisir l’encodage approprié</h3> <p> Dans la quasi-totalité des projets modernes, le choix le plus simple et le plus robuste consiste à adopter <strong>UTF‑8</strong> pour l’ensemble du site. Cet encodage couvre tous les besoins courants, depuis les sites monolingues jusqu’aux plateformes multilingues complexes. </p> <h3 id="2-configurer-l-editeur-de-code-et-les-fichiers-sources">2. Configurer l’éditeur de code et les fichiers sources</h3> <p> Veillez à ce que votre éditeur de code (ou environnement de développement intégré) enregistre tous les fichiers HTML, CSS et scripts contenant du texte en <strong>UTF‑8</strong>. La plupart des éditeurs modernes proposent une option pour définir l’encodage par défaut du projet. </p> <h3 id="3-mettre-a-jour-les-modeles-html">3. Mettre à jour les modèles HTML</h3> <p> Ajoutez ou modifiez la balise charset dans vos modèles de page (layouts, thèmes de CMS, templates de framework) afin qu’ils incluent systématiquement : </p> <pre><code><meta charset="UTF-8"> </code></pre> <p> Assurez-vous qu’il n’existe pas d’autre balise charset contradictoire au sein du même document. </p> <h3 id="4-verifier-la-configuration-serveur">4. Vérifier la configuration serveur</h3> <p> Adaptez la configuration de votre serveur web pour qu’il envoie des en-têtes HTTP cohérents, par exemple : </p> <pre><code>Content-Type: text/html; charset=UTF-8 </code></pre> <p> Cette configuration peut se faire via les fichiers de configuration d’Apache ou de Nginx, via un fichier <code>.htaccess</code>, ou directement au niveau de votre application, selon l’architecture utilisée. </p> <h3 id="5-tester-et-valider">5. Tester et valider</h3> <p> Une fois la configuration mise à jour, testez différentes pages du site : </p> <ul> <li>vérifiez l’affichage des caractères accentués, des guillemets typographiques, des tirets et des symboles spécifiques à votre langue ;</li> <li>utilisez les outils de développement de votre navigateur pour contrôler l’encodage détecté ;</li> <li>passez vos pages dans un validateur HTML pour détecter d’éventuelles erreurs résiduelles.</li> </ul> <h2 id="resume-des-points-essentiels">Résumé des points essentiels</h2> <p> Pour conclure ce tour d’horizon, retenez les points clés suivants concernant la balise <strong>meta charset</strong> : </p> <ul> <li>elle <strong>définit l’encodage des caractères</strong> de votre page HTML ;</li> <li>la forme moderne recommandée en HTML5 est <code><meta charset="UTF-8"></code> ;</li> <li>la balise charset doit être placée <strong>le plus tôt possible dans le <head></strong> pour être correctement interprétée ;</li> <li><strong>UTF‑8</strong> est l’encodage dominant et recommandé pour le web, utilisé par l’immense majorité des sites ;</li> <li>la balise charset n’est pas un facteur de <strong>ranking direct</strong>, mais un encodage cohérent est indispensable pour une bonne <strong>lisibilité</strong> et une <strong>indexation fiable</strong> ;</li> <li>il est crucial d’assurer une <strong>cohérence totale de la chaîne d’encodage</strong> (fichiers, serveur, base de données, exports) ;</li> <li>la balise charset joue un rôle technique différent des autres meta tags orientés SEO comme <code><title></code>, <code>meta description</code>, <code>meta robots</code> ou <code>meta viewport</code>.</li> </ul> <p> En respectant ces bonnes pratiques, vous garantissez à la fois un <strong>affichage irréprochable des caractères</strong>, une meilleure accessibilité et une base technique solide pour votre <strong>SEO on-page</strong>. </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="on-page-seo-nbsp-balise-meta-charset-et-encodage-des-caracteres-en-html"> <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 = 'on-page-seo-nbsp-balise-meta-charset-et-encodage-des-caracteres-en-html'; // 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>