Que dit Google sur le SEO ? /
Quiz SEO Express

Testez vos connaissances SEO en 3 questions

Moins de 30 secondes. Decouvrez ce que vous savez vraiment sur le referencement Google.

🕒 ~30s 🎯 3 questions 📚 SEO Google

Declaration officielle

Google ne se préoccupe pas vraiment de l'extension du fichier (.py, .txt, .php). Ce qui compte est le content-type header envoyé par le serveur et le contenu réel retourné lors de l'accès à l'URL.
🎥 Vidéo source

Extrait d'une vidéo Google Search Central

💬 EN 📅 08/09/2022 ✂ 12 déclarations
Voir sur YouTube →
Autres déclarations de cette vidéo 11
  1. Google indexe-t-il vraiment vos PDF ou les transforme-t-il d'abord ?
  2. Le poids du contenu varie-t-il selon son emplacement en HTML et en PDF ?
  3. Google dépend-il vraiment d'Adobe pour indexer vos PDF ?
  4. Google indexe-t-il vraiment le code source comme du texte ordinaire ?
  5. Pourquoi les fichiers de code source peinent-ils à se classer dans Google ?
  6. Faut-il vraiment arrêter de stocker tous vos PDF dans un dossier /pdfs/ ?
  7. Pourquoi Google n'indexe-t-il jamais une image isolée sans page d'hébergement ?
  8. Google indexe-t-il vraiment les images et vidéos différemment du texte ?
  9. Google filtre-t-il les données personnelles avant indexation ?
  10. Google indexe-t-il vraiment tous vos fichiers XML ?
  11. Peut-on vraiment indexer des fichiers JSON et texte brut sans méta-données ?
📅
Declaration officielle du (il y a 3 ans)
TL;DR

Google ignore complètement l'extension des fichiers dans vos URLs. Ce qui compte réellement : le content-type header envoyé par votre serveur et le contenu effectif de la page. Un fichier .py peut être indexé comme une page HTML si le serveur le déclare correctement.

Ce qu'il faut comprendre

Pourquoi l'extension de fichier semble-t-elle importante alors qu'elle ne l'est pas ?

L'extension d'un fichier (`.html`, `.php`, `.txt`, `.pdf`) est un vestige de l'architecture web historique. Elle indiquait autrefois le type de contenu ou le langage côté serveur utilisé. Beaucoup de SEO pensent encore que ces extensions influencent l'indexation.

Soyons honnêtes : Google ne lit pas l'extension pour comprendre votre page. Le moteur se fie exclusivement au content-type header HTTP que votre serveur envoie lors de la réponse, et au contenu réel retourné. Si votre serveur déclare `text/html` pour un fichier `.py`, Google le traite comme du HTML.

Qu'est-ce que le content-type header concrètement ?

Le content-type header est une ligne d'en-tête HTTP envoyée par le serveur qui indique au navigateur (et à Googlebot) quel type de contenu il reçoit. Exemples : `text/html` pour du HTML, `application/pdf` pour un PDF, `text/plain` pour du texte brut.

C'est cette déclaration qui guide Google, pas le `.php` ou `.html` dans l'URL. Si votre serveur envoie un content-type incohérent avec le contenu réel, vous créez de la confusion.

Que se passe-t-il si le content-type est mal configuré ?

Si votre serveur déclare `text/plain` pour une page HTML, Google va interpréter le contenu comme du texte brut. Vos balises ``, `<meta>`, vos liens — tout sera ignoré ou mal indexé.</p><p>À l'inverse, un fichier avec une extension inhabituelle (`.aspx`, `.cfm`, `.py`) sera parfaitement indexé si le content-type est `text/html` et que le contenu correspond. <strong>Le problème n'est jamais l'extension, toujours la configuration serveur.</strong></p><ul><li>Google lit le <strong>content-type header HTTP</strong>, pas l'extension de fichier dans l'URL</li><li>L'extension (`.html`, `.php`, `.txt`) n'a aucun impact direct sur l'indexation</li><li>Un mauvais content-type peut bloquer l'indexation même si l'extension semble correcte</li><li>Le contenu réel retourné doit correspondre au content-type déclaré</li><li>Cette règle s'applique à tous les types de fichiers crawlés par Google</li></ul></div> </div> <div class="section-card ai-section"> <h2>Avis d'un expert SEO</h2> <div class="content" data-field="ai_expert_opinion"><h3>Cette déclaration est-elle cohérente avec les observations terrain ?</h3><p>Oui, totalement. Depuis des années, on indexe sans problème des URLs sans extension (ex: `/produits/chaussures`) ou avec des extensions exotiques. Les sites sous frameworks modernes (React, Vue, Next.js) génèrent souvent des URLs sans `.html`, et Google les indexe parfaitement.</p><p>Ce qui coince parfois : les serveurs mal configurés qui envoient des headers HTTP incorrects. Un serveur Apache mal paramétré peut renvoyer `text/plain` par défaut pour des fichiers inconnus, cassant ainsi l'indexation.</p><h3>Faut-il pour autant ignorer complètement l'extension dans vos URLs ?</h3><p>Non, et c'est là que la nuance compte. L'extension n'a pas d'impact direct sur le <strong>crawl et l'indexation</strong>, mais elle peut influencer indirectement le <strong>comportement utilisateur</strong> et la perception de vos URLs.</p><p>Une URL finissant par `.pdf` signale clairement qu'on va télécharger un document. Une URL `/article.txt` peut dérouter l'internaute. Pour des raisons d'UX et de <strong>taux de clic dans les SERPs</strong>, des URLs propres sans extension ou avec `.html` restent préférables. Ce n'est plus du SEO technique, c'est de l'ergonomie.</p><h3>Dans quels cas cette règle pose-t-elle problème ?</h3><p>Attention aux <strong>serveurs qui servent des contenus multiples via la même URL</strong> selon le user-agent. Si votre serveur renvoie du HTML à Googlebot mais du JSON à un bot API, vous créez un décalage entre ce que Google indexe et ce que d'autres systèmes voient. <strong>[À vérifier]</strong> dans vos logs pour éviter les incohérences.</p><p>Autre piège : les CDN ou reverse proxies qui modifient les headers HTTP en cache. Si votre origine envoie le bon content-type mais que Cloudflare ou Fastly le remplace par un header générique, Google indexe le mauvais format.</p><div class="alert"><strong>Alerte configuration serveur :</strong> Ne vous fiez jamais à l'extension dans vos URLs pour diagnostiquer un problème d'indexation. Vérifiez toujours les headers HTTP réels envoyés par votre serveur avec un outil comme curl ou les DevTools de Chrome. C'est le seul moyen fiable de confirmer ce que Google reçoit.</div></div> </div> <div class="section-card ai-section"> <h2>Impact pratique et recommandations</h2> <div class="content" data-field="ai_practical_impact"><h3>Que faut-il vérifier concrètement sur votre site ?</h3><p>Première étape : <strong>auditez vos headers HTTP</strong>. Utilisez `curl -I https://votresite.com/page` ou les DevTools (onglet Network) pour vérifier que chaque type de page renvoie le bon content-type. Une page HTML doit renvoyer `text/html; charset=UTF-8`, un PDF `application/pdf`, etc.</p><p>Deuxième étape : vérifiez que vos fichiers statiques (CSS, JS, images) ont aussi les bons headers. Un CSS servi en `text/plain` peut ne pas être exécuté correctement par le navigateur, dégradant le rendering côté Google.</p><h3>Quelles erreurs éviter absolument ?</h3><p>Ne configurez jamais votre serveur pour forcer un content-type basé uniquement sur l'extension visible dans l'URL. Si vous rewritez `/article` vers `/article.php` en interne, assurez-vous que le header HTTP final est bien `text/html`, pas `application/x-httpd-php`.</p><p>Évitez les setups où le content-type varie selon le user-agent sans raison valable. Google déteste le cloaking, même involontaire. Si vous servez du contenu différent à Googlebot et aux utilisateurs, vous risquez une pénalité manuelle.</p><h3>Comment garantir une configuration serveur optimale ?</h3><p>Pour Apache, vérifiez votre fichier `.htaccess` ou `httpd.conf` — la directive `AddType` doit être cohérente. Pour Nginx, contrôlez le bloc `types {}` dans `nginx.conf`. Pour les serveurs Node.js, Express ou Fastify gèrent automatiquement les content-types, mais vérifiez vos middlewares personnalisés.</p><ul class="checklist"><li>Auditer tous les content-type headers avec curl ou DevTools</li><li>Corriger les incohérences entre extension et header HTTP</li><li>Tester les URLs sans extension pour confirmer qu'elles renvoient le bon type</li><li>Vérifier que les rewrites internes ne cassent pas les headers</li><li>Monitorer les logs serveur pour repérer les erreurs 500 liées aux types MIME</li><li>Documenter la configuration serveur pour éviter les régressions lors des déploiements</li></ul><div class="summary">L'extension de fichier n'a aucun impact direct sur l'indexation Google, mais une mauvaise configuration des content-type headers peut bloquer complètement votre référencement. Auditez vos headers HTTP, corrigez les incohérences, et assurez-vous que votre serveur déclare correctement le type de chaque ressource. Ces optimisations techniques peuvent sembler simples en théorie, mais elles touchent souvent à la configuration serveur, aux règles de cache et aux rewrites internes — des zones où une erreur peut avoir des conséquences massives. Si votre infrastructure web est complexe ou si vous n'êtes pas à l'aise avec les configurations Apache/Nginx, faire appel à une agence SEO technique expérimentée vous évitera des erreurs coûteuses et garantira une mise en conformité rapide et durable.</div></div> </div> <!-- FAQ --> <div class="section-card"> <h2>❓ Questions frequentes</h2> <div class="faq-list"> <details class="faq-item"> <summary class="faq-question">Puis-je utiliser des URLs sans extension comme /produits/chaussures pour le SEO ?</summary> <div class="faq-answer">Oui, absolument. Google indexe parfaitement les URLs sans extension tant que le serveur envoie le bon content-type header (text/html pour une page web). C'est même devenu la norme pour les sites modernes.</div> </details> <details class="faq-item"> <summary class="faq-question">Mon site affiche des fichiers .php dans les URLs, dois-je les masquer ?</summary> <div class="faq-answer">Ce n'est pas une obligation technique pour l'indexation, mais pour l'UX et la propreté des URLs, oui. Utilisez un rewrite Apache/Nginx pour transformer /page.php en /page, tout en gardant le bon content-type header.</div> </details> <details class="faq-item"> <summary class="faq-question">Comment vérifier le content-type header envoyé par mon serveur ?</summary> <div class="faq-answer">Utilisez la commande curl -I https://votresite.com/page dans un terminal, ou l'onglet Network des DevTools Chrome/Firefox. Cherchez la ligne 'content-type' dans les headers de réponse HTTP.</div> </details> <details class="faq-item"> <summary class="faq-question">Un fichier .txt peut-il être indexé comme une page HTML ?</summary> <div class="faq-answer">Oui, si votre serveur envoie un content-type: text/html et que le contenu est du HTML valide. L'extension .txt n'empêche pas l'indexation, mais attention à la cohérence pour ne pas dérouter les utilisateurs.</div> </details> <details class="faq-item"> <summary class="faq-question">Que se passe-t-il si mon CDN modifie le content-type header ?</summary> <div class="faq-answer">Google indexera selon le header final reçu par Googlebot, pas celui de votre serveur origine. Vérifiez les règles de cache de votre CDN (Cloudflare, Fastify, AWS CloudFront) pour garantir que les headers critiques sont préservés.</div> </details> </div> </div> <!-- Tags SEO --> <div class="seo-tags-block"> <span class="seo-tags-label">🏷 Sujets associes</span> <div class="seo-tags"> <a href="/?q=content-type" class="seo-tag-pill">content-type</a> <a href="/?q=headers+HTTP" class="seo-tag-pill">headers HTTP</a> <a href="/?q=indexation" class="seo-tag-pill">indexation</a> <a href="/?q=URLs" class="seo-tag-pill">URLs</a> <a href="/?q=configuration+serveur" class="seo-tag-pill">configuration serveur</a> <a href="/?q=crawl" class="seo-tag-pill">crawl</a> <a href="/?q=architecture+web" class="seo-tag-pill">architecture web</a> </div> </div> <!-- Categories --> <div class="declaration-tags"> <a href="/c/contenu" class="tag" style="background: #10b98120; color: #10b981;">Contenu</a> <a href="/c/ia-seo" class="tag" style="background: #a855f720; color: #a855f7;">IA & SEO</a> <a href="/c/nom-domaine" class="tag" style="background: #7c3aed20; color: #7c3aed;">Nom de domaine</a> <a href="/c/pdf-fichiers" class="tag" style="background: #78716c20; color: #78716c;">PDF & Fichiers</a> </div> <!-- Syntheses thematiques liees --> <!-- Autres declarations de la meme video YouTube --> <div class="section-card yt-same-video"> <h2> 🎥 De la même vidéo <span class="yt-same-count">11</span> </h2> <p class="yt-same-intro"> Autres enseignements SEO extraits de cette même vidéo Google Search Central · publiée le 08/09/2022 </p> <div class="related-grid yt-same-grid"> <a href="/d/google-convertit-les-pdf-en-html-pour-l-indexation" class="related-item yt-same-item"> <div class="title">Google indexe-t-il vraiment vos PDF ou les transforme-t-il d'abord ?</div> <div class="meta"> </div> </a> <a href="/d/poids-uniforme-du-contenu-dans-les-pdf-vs-html" class="related-item yt-same-item"> <div class="title">Le poids du contenu varie-t-il selon son emplacement en HTML et en PDF ?</div> <div class="meta"> </div> </a> <a href="/d/google-utilise-adobe-pour-convertir-les-pdf" class="related-item yt-same-item"> <div class="title">Google dépend-il vraiment d'Adobe pour indexer vos PDF ?</div> <div class="meta"> </div> </a> <a href="/d/google-indexe-le-code-source-comme-du-texte" class="related-item yt-same-item"> <div class="title">Google indexe-t-il vraiment le code source comme du texte ordinaire ?</div> <div class="meta"> </div> </a> <a href="/d/les-fichiers-de-code-ont-du-mal-a-bien-se-classer" class="related-item yt-same-item"> <div class="title">Pourquoi les fichiers de code source peinent-ils à se classer dans Google ?</div> <div class="meta"> </div> </a> <a href="/d/meilleure-pratique-repartir-les-pdf-par-sujet-pas-par-type" class="related-item yt-same-item"> <div class="title">Faut-il vraiment arrêter de stocker tous vos PDF dans un dossier /pdfs/ ?</div> <div class="meta"> </div> </a> <a href="/d/google-images-necessite-une-page-d-hebergement" class="related-item yt-same-item"> <div class="title">Pourquoi Google n'indexe-t-il jamais une image isolée sans page d'hébergement ?</div> <div class="meta"> </div> </a> <a href="/d/images-et-videos-utilisent-un-indexeur-different" class="related-item yt-same-item"> <div class="title">Google indexe-t-il vraiment les images et vidéos différemment du texte ?</div> <div class="meta"> </div> </a> <a href="/d/google-n-analyse-pas-le-contenu-prive-dans-les-documents" class="related-item yt-same-item"> <div class="title">Google filtre-t-il les données personnelles avant indexation ?</div> <div class="meta"> </div> </a> <a href="/d/google-peut-indexer-certains-fichiers-xml-mais-pas-tous" class="related-item yt-same-item"> <div class="title">Google indexe-t-il vraiment tous vos fichiers XML ?</div> <div class="meta"> </div> </a> <a href="/d/fichiers-json-et-texte-indexables-avec-contexte-externe" class="related-item yt-same-item"> <div class="title">Peut-on vraiment indexer des fichiers JSON et texte brut sans méta-données ?</div> <div class="meta"> </div> </a> </div> <a href="https://www.youtube.com/watch?v=2gXO6qpcQXY" target="_blank" rel="noopener" class="yt-same-source-link"> 🎥 Voir la vidéo complète sur YouTube → </a> </div> <!-- Declarations similaires --> <div class="section-card"> <h2>Declarations similaires</h2> <div class="related-grid"> <a href="/d/le-seo-est-complexe-multiforme-et-resilient" class="related-item"> <div class="title">Peut-on vraiment se permettre de faire n'importe quoi en SEO sans conséquences ?</div> <div class="meta"> John Mueller · 28/04/2026 · <span class="stars">★★</span> </div> </a> <a href="/d/celui-qui-dit-tout-savoir-sur-le-seo-est-un-imposteur" class="related-item"> <div class="title">Pourquoi personne ne peut vraiment maîtriser le SEO à 100% ?</div> <div class="meta"> John Mueller · 28/04/2026 · <span class="stars">★★★</span> </div> </a> <a href="/d/utilisation-de-bigquery-pour-analyser-les-sites-web" class="related-item"> <div class="title">BigQuery est-il vraiment indispensable pour analyser vos données SEO à grande échelle ?</div> <div class="meta"> Martin Splitt · 23/04/2026 · <span class="stars">★★★</span> </div> </a> <a href="/d/recommandations-sur-la-taille-des-fichiers-robots-txt" class="related-item"> <div class="title">Faut-il vraiment respecter la limite de 100KB pour votre fichier robots.txt ?</div> <div class="meta"> Martin Splitt · 23/04/2026 · <span class="stars">★★</span> </div> </a> <a href="/d/usage-de-custom-javascript-metrics" class="related-item"> <div class="title">Google utilise-t-il des scripts JavaScript personnalisés pour évaluer vos pages ?</div> <div class="meta"> Martin Splitt · 23/04/2026 · <span class="stars">★★★</span> </div> </a> <a href="/d/nouvelle-collecte-de-donnees-robots-txt-avec-http-archive" class="related-item"> <div class="title">Pourquoi Google publie-t-il soudainement des données massives sur l'usage des robots.txt ?</div> <div class="meta"> Gary Illyes · 23/04/2026 · <span class="stars">★★★</span> </div> </a> </div> </div> <!-- Prev/Next --> <div class="prev-next"> <a href="/d/fichiers-json-et-texte-indexables-avec-contexte-externe"> <div class="nav-label">« Precedent</div> <div class="nav-title">Fichiers JSON et texte indexables avec contexte ex...</div> </a> <a href="/d/google-convertit-les-pdf-en-html-pour-l-indexation" style="text-align: right;"> <div class="nav-label">Suivant »</div> <div class="nav-title">Google convertit les PDF en HTML pour l'indexation...</div> </a> </div> <div class="back-link"> <a href="/" class="btn">« Retour aux resultats</a> </div> </div> <style> .comments-section { max-width: 860px; margin: 40px auto; padding: 0 20px; } .comments-title { font-size: 1.3rem; font-weight: 700; color: #1e293b; margin: 0 0 20px; padding-bottom: 12px; border-bottom: 2px solid #f1f5f9; } .comments-count { font-weight: 400; font-size: .9rem; color: #94a3b8; } .comments-empty { color: #94a3b8; font-style: italic; margin-bottom: 24px; } .comments-list { margin-bottom: 28px; } .comment-item { padding: 16px 0; border-bottom: 1px solid #f1f5f9; } .comment-item:last-child { border-bottom: none; } .comment-pending { background: #fffbeb; border-left: 3px solid #f59e0b; padding-left: 14px; border-radius: 6px; margin: 4px 0; } .comment-pending-badge { display: inline-block; font-size: .7rem; font-weight: 600; color: #92400e; background: #fef3c7; padding: 1px 8px; border-radius: 10px; margin-left: 8px; } .comment-meta { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; } .comment-author { font-weight: 600; font-size: .95rem; color: #1e293b; } .comment-date { font-size: .8rem; color: #94a3b8; } .comment-body { font-size: .93rem; line-height: 1.6; color: #475569; } .comment-form { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 12px; padding: 24px; position: relative; } .comment-form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 16px; } .comment-form-field { margin-bottom: 12px; } .comment-form-field label { display: block; font-size: .85rem; font-weight: 600; color: #475569; margin-bottom: 4px; } .comment-form-field input, .comment-form-field textarea { width: 100%; padding: 10px 12px; border: 1px solid #d1d5db; border-radius: 8px; font-size: .9rem; font-family: inherit; background: #fff; transition: border-color .2s; box-sizing: border-box; } .comment-form-field input:focus, .comment-form-field textarea:focus { outline: none; border-color: #1a73e8; box-shadow: 0 0 0 3px rgba(26, 115, 232, .1); } .comment-form-field textarea { resize: vertical; min-height: 100px; } .comment-form-charcount { font-size: .75rem; color: #94a3b8; text-align: right; margin-top: 4px; } .comment-form-footer { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; } .comment-submit-btn { padding: 10px 24px; background: #1a73e8; color: #fff; border: none; border-radius: 8px; font-size: .9rem; font-weight: 600; cursor: pointer; transition: opacity .2s; } .comment-submit-btn:hover { opacity: .88; } .comment-submit-btn:disabled { opacity: .5; cursor: default; } .comment-form-notice { font-size: .8rem; color: #94a3b8; font-style: italic; } .comment-feedback { margin-top: 12px; padding: 10px 14px; border-radius: 8px; font-size: .9rem; font-weight: 500; } .comment-feedback-success { background: #d1fae5; color: #065f46; } .comment-feedback-error { background: #fee2e2; color: #991b1b; } @media (max-width: 768px) { .comment-form-row { grid-template-columns: 1fr; gap: 0; } .comments-section { padding: 0 16px; } } </style> <section class="comments-section" id="comments"> <h3 class="comments-title">💬 Commentaires <span class="comments-count">(0)</span></h3> <div class="comments-list" id="comments-list"> <p class="comments-empty" id="comments-empty">Soyez le premier à commenter.</p> </div> <form class="comment-form" id="comment-form" novalidate> <input type="hidden" name="page_type" value="declaration"> <input type="hidden" name="page_id" value="3045"> <input type="hidden" name="lang" value="fr"> <!-- Honeypot anti-spam --> <div style="position:absolute;left:-9999px;" aria-hidden="true"> <label for="website">Do not fill this field</label> <input type="text" id="website" name="website" tabindex="-1" autocomplete="off"> </div> <div class="comment-form-row"> <div class="comment-form-field"> <label for="comment-name">Nom ou pseudo *</label> <input type="text" id="comment-name" name="author_name" required maxlength="100" autocomplete="name"> </div> <div class="comment-form-field"> <label for="comment-email">Email (facultatif, non publié)</label> <input type="email" id="comment-email" name="author_email" maxlength="255" autocomplete="email"> </div> </div> <div class="comment-form-field"> <label for="comment-content">Votre commentaire *</label> <textarea id="comment-content" name="content" required maxlength="2000" rows="4"></textarea> <div class="comment-form-charcount"><span id="comment-chars">2000</span> caractères restants</div> </div> <div class="comment-form-footer"> <button type="submit" class="comment-submit-btn">Publier le commentaire</button> <span class="comment-form-notice">Les commentaires sont modérés avant publication.</span> </div> <div id="comment-feedback" class="comment-feedback" style="display:none;"></div> </form> </section> <script> (function() { var form = document.getElementById('comment-form'); if (!form) return; var textarea = document.getElementById('comment-content'); var charsEl = document.getElementById('comment-chars'); var feedback = document.getElementById('comment-feedback'); var API_URL = 'https://seoclaims.com/api/comments.php'; // Compteur de caracteres textarea.addEventListener('input', function() { var remaining = 2000 - this.value.length; charsEl.textContent = remaining; charsEl.style.color = remaining < 100 ? '#dc2626' : ''; }); form.addEventListener('submit', function(e) { e.preventDefault(); var btn = form.querySelector('.comment-submit-btn'); btn.disabled = true; var origText = btn.textContent; btn.textContent = '...'; feedback.style.display = 'none'; var data = { action: 'submit', page_type: form.querySelector('[name="page_type"]').value, page_id: parseInt(form.querySelector('[name="page_id"]').value), author_name: form.querySelector('[name="author_name"]').value.trim(), author_email: form.querySelector('[name="author_email"]').value.trim(), content: form.querySelector('[name="content"]').value.trim(), lang: form.querySelector('[name="lang"]').value, website: form.querySelector('[name="website"]').value }; fetch(API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }) .then(function(r) { return r.json(); }) .then(function(res) { if (res.success) { feedback.className = 'comment-feedback comment-feedback-success'; feedback.textContent = res.message; feedback.style.display = 'block'; // Inserer le commentaire en ligne (en attente de moderation) var list = document.getElementById('comments-list'); var empty = document.getElementById('comments-empty'); if (empty) empty.remove(); var pendingLabel = 'En attente de moderation'; var now = new Date(); var day = String(now.getDate()).padStart(2,'0'); var month = String(now.getMonth()+1).padStart(2,'0'); var dateStr = day + '/' + month + '/' + now.getFullYear(); var div = document.createElement('div'); div.className = 'comment-item comment-pending'; div.innerHTML = '<div class="comment-meta">' + '<span class="comment-author">' + data.author_name.replace(/</g,'<') + '</span>' + '<span class="comment-date">' + dateStr + '</span>' + '<span class="comment-pending-badge">' + pendingLabel + '</span>' + '</div>' + '<div class="comment-body">' + data.content.replace(/</g,'<').replace(/\n/g,'<br>') + '</div>'; list.appendChild(div); form.querySelector('[name="content"]').value = ''; form.querySelector('[name="author_name"]').value = ''; form.querySelector('[name="author_email"]').value = ''; charsEl.textContent = '2000'; } else { feedback.className = 'comment-feedback comment-feedback-error'; feedback.textContent = res.error; feedback.style.display = 'block'; btn.disabled = false; btn.textContent = origText; } }) .catch(function() { feedback.className = 'comment-feedback comment-feedback-error'; feedback.textContent = 'Erreur réseau'; feedback.style.display = 'block'; btn.disabled = false; btn.textContent = origText; }); }); })(); </script> <section class="nl-inline-section"> <div class="nl-inline-inner"> <div class="nl-inline-icon">🔔</div> <div class="nl-inline-text"> <h2 class="nl-inline-headline">Recevez une analyse complète en temps réel des dernières déclarations de Google</h2> <p class="nl-inline-sub">Soyez alerté à chaque nouvelle déclaration officielle Google SEO — avec l'analyse complète incluse.</p> </div> <div class="nl-inline-action"> <form class="nl-inline-form" id="nl-inline-form" novalidate> <input type="email" name="email" placeholder="Votre adresse email" required autocomplete="email"> <button type="submit">S'inscrire gratuitement</button> </form> <div class="nl-inline-privacy">Aucun spam. Désinscription en 1 clic.</div> </div> </div> </section> <style> .nl-inline-section { background: linear-gradient(135deg, #0f172a 0%, #1e3a5f 50%, #0f172a 100%); padding: 52px 24px; margin: 0; } .nl-inline-inner { max-width: 960px; margin: 0 auto; display: grid; grid-template-columns: auto 1fr auto; gap: 24px 32px; align-items: center; } .nl-inline-icon { font-size: 40px; line-height: 1; } .nl-inline-headline { font-size: clamp(16px, 2vw, 20px); font-weight: 700; color: #f0f9ff; margin: 0 0 6px; line-height: 1.35; } .nl-inline-sub { font-size: 14px; color: #7fb8d8; margin: 0; line-height: 1.55; } .nl-inline-action { min-width: 300px; } .nl-inline-form { display: flex; gap: 8px; } .nl-inline-form input[type="email"] { flex: 1; padding: 11px 14px; border: 1px solid rgba(14,165,233,.4); border-radius: 8px; background: rgba(255,255,255,.07); color: #f0f9ff; font-size: 14px; outline: none; min-width: 0; transition: border-color .2s, background .2s; } .nl-inline-form input[type="email"]::placeholder { color: #5a91b0; } .nl-inline-form input[type="email"]:focus { border-color: #0ea5e9; background: rgba(255,255,255,.12); } .nl-inline-form button { padding: 11px 18px; background: linear-gradient(135deg, #0ea5e9, #7c3aed); color: #fff; border: none; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; white-space: nowrap; transition: opacity .2s; } .nl-inline-form button:hover { opacity: .88; } .nl-inline-form button:disabled { opacity: .55; cursor: default; } .nl-inline-privacy { font-size: 12px; color: #4a7a96; margin-top: 7px; text-align: center; } @media (max-width: 768px) { .nl-inline-inner { grid-template-columns: auto 1fr; grid-template-rows: auto auto; } .nl-inline-action { grid-column: 1 / -1; min-width: 0; } .nl-inline-form { flex-wrap: wrap; } .nl-inline-form input[type="email"] { min-width: 200px; } } </style> <script> (function() { var form = document.getElementById('nl-inline-form'); if (!form) return; var LANG = 'fr'; var API_URL = 'https://seoclaims.com/api/subscribe'; var COOKIE = 'nl_subscribed'; var MSG = { success: 'Parfait ! Vous recevrez les prochaines analyses.', already: 'Vous êtes déjà inscrit(e).', error: 'Une erreur est survenue, veuillez réessayer.', }; function getCookie(n) { return document.cookie.split(';').some(function(c) { return c.trim().startsWith(n + '='); }); } function setCookie(n, days) { var d = new Date(); d.setDate(d.getDate() + days); document.cookie = n + '=1;expires=' + d.toUTCString() + ';path=/;SameSite=Lax'; } // Masquer si deja inscrit if (getCookie(COOKIE)) { var section = form.closest('.nl-inline-section'); if (section) section.style.display = 'none'; return; } form.addEventListener('submit', function(e) { e.preventDefault(); var email = form.querySelector('input[type="email"]').value.trim(); var btn = form.querySelector('button'); btn.disabled = true; var orig = btn.textContent; btn.textContent = '...'; fetch(API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: email, lang: LANG }) }) .then(function(r) { return r.json(); }) .then(function(data) { if (data.success) { setCookie(COOKIE, 365); form.innerHTML = '<div style="color:#0d9488;font-weight:600;font-size:15px;padding:10px 0;">✓ ' + (data.already ? MSG.already : MSG.success) + '</div>'; // Masquer aussi banniere footer et popup si presents var banner = document.getElementById('nl-banner'); var popup = document.getElementById('nl-popup'); if (banner) banner.style.display = 'none'; if (popup) popup.style.display = 'none'; } else { btn.disabled = false; btn.textContent = orig; var err = form.querySelector('.nl-inline-err'); if (!err) { err = document.createElement('div'); err.className = 'nl-inline-err'; err.style.cssText = 'color:#dc2626;font-size:13px;margin-top:6px;'; form.parentNode.insertBefore(err, form.nextSibling); } err.textContent = data.error || MSG.error; } }) .catch(function() { btn.disabled = false; btn.textContent = orig; }); }); })(); </script> <footer class="site-footer"> <div class="footer-inner"> <!-- ====== Row 1 : About + Navigation + Resources ====== --> <div class="footer-top"> <div> <div class="footer-brand"> <picture> <source srcset="https://seoclaims.com/assets/logo.webp" type="image/webp"> <img src="https://seoclaims.com/assets/logo.png" alt="SEO Declarations" class="footer-logo" loading="lazy" width="132" height="72"> </picture> </div> <p>SEO Claims recense, analyse et traduit les <a href="/declarations">declarations officielles de Google</a> sur le referencement naturel, issues des <a href="/articles">articles publies</a> et des <a href="/videos">videos YouTube</a> de Google Search Central. Chaque declaration est enrichie par une <a href="/declarations">analyse IA</a>, classee par <a href="/declarations">categorie SEO</a> et attribuee a son <a href="/speaker/john-mueller">auteur</a>. Un outil indispensable pour les professionnels du SEO qui veulent savoir exactement ce que Google recommande.</p> </div> <div> <div class="footer-heading">Navigation</div> <nav class="footer-nav"> <a href="/declarations" class="footer-link">Declarations</a> <a href="/labs/" class="footer-link">Labs SEO</a> <a href="/speaker/john-mueller" class="footer-link">Auteurs</a> <a href="/sitemap-declarations" class="footer-link">Plan du site</a> <a href="/comparatif-agences-seo" class="footer-link">Comparatif Agences SEO</a> <a href="/mentions-legales" class="footer-link">Mentions legales</a> </nav> </div> <div> <div class="footer-heading">Ressources</div> <nav class="footer-nav"> <a href="https://search.google.com/search-console" target="_blank" rel="noopener" class="footer-link">Google Search Console</a> <a href="https://pagespeed.web.dev/" target="_blank" rel="noopener" class="footer-link">PageSpeed Insights</a> <a href="https://search.google.com/test/rich-results" target="_blank" rel="noopener" class="footer-link">Rich Results Test</a> <a href="https://developer.chrome.com/docs/lighthouse/" target="_blank" rel="noopener" class="footer-link">Lighthouse</a> <a href="https://developers.google.com/search/docs" target="_blank" rel="noopener" class="footer-link">Google Search Guidelines</a> <a href="/outils-google" class="footer-link" style="color: #60a5fa; font-weight: 600;">Tous les outils Google →</a> </nav> </div> </div> <!-- ====== Row 2 : 3 piliers SEO — Top 5 categories par vues ====== --> <div class="footer-pillars"> <!-- Semantique --> <details class="footer-pillar-details" open> <summary class="footer-pillar-title"> <span style="display:flex;align-items:center;gap:8px;"> <span class="footer-pillar-dot" style="background: #60a5fa;"></span> <span class="footer-pillar-label" style="color: #60a5fa;">Semantique</span> </span> </summary> <nav class="footer-pillar-nav"> <a href="/c/ia-seo" class="footer-pillar-link"> <span>IA & SEO</span> <span class="footer-pillar-count">9673</span> </a> <a href="/c/contenu" class="footer-pillar-link"> <span>Contenu</span> <span class="footer-pillar-count">5585</span> </a> <a href="/c/nom-domaine" class="footer-pillar-link"> <span>Nom de domaine</span> <span class="footer-pillar-count">1943</span> </a> <a href="/c/pdf-fichiers" class="footer-pillar-link"> <span>PDF & Fichiers</span> <span class="footer-pillar-count">497</span> </a> <a href="/c/discover-actualites" class="footer-pillar-link"> <span>Discover & Actualites</span> <span class="footer-pillar-count">343</span> </a> </nav> </details> <!-- Technique --> <details class="footer-pillar-details" open> <summary class="footer-pillar-title"> <span style="display:flex;align-items:center;gap:8px;"> <span class="footer-pillar-dot" style="background: #34d399;"></span> <span class="footer-pillar-label" style="color: #34d399;">Technique</span> </span> </summary> <nav class="footer-pillar-nav"> <a href="/c/anciennete-historique" class="footer-pillar-link"> <span>Anciennete & Historique</span> <span class="footer-pillar-count">6840</span> </a> <a href="/c/crawl-indexation" class="footer-pillar-link"> <span>Crawl & Indexation</span> <span class="footer-pillar-count">3560</span> </a> <a href="/c/javascript-technique" class="footer-pillar-link"> <span>JavaScript & Technique</span> <span class="footer-pillar-count">2358</span> </a> <a href="/c/search-console" class="footer-pillar-link"> <span>Search Console</span> <span class="footer-pillar-count">1848</span> </a> <a href="/c/performance-web" class="footer-pillar-link"> <span>Performance Web</span> <span class="footer-pillar-count">105</span> </a> </nav> </details> <!-- Autorite --> <details class="footer-pillar-details" open> <summary class="footer-pillar-title"> <span style="display:flex;align-items:center;gap:8px;"> <span class="footer-pillar-dot" style="background: #fbbf24;"></span> <span class="footer-pillar-label" style="color: #fbbf24;">Autorite</span> </span> </summary> <nav class="footer-pillar-nav"> <a href="/c/liens-backlinks" class="footer-pillar-link"> <span>Liens & Backlinks</span> <span class="footer-pillar-count">2076</span> </a> <a href="/c/reseaux-sociaux" class="footer-pillar-link"> <span>Reseaux sociaux</span> <span class="footer-pillar-count">541</span> </a> <a href="/c/penalites-spam" class="footer-pillar-link"> <span>Penalites & Spam</span> <span class="footer-pillar-count">515</span> </a> <a href="/c/algorithmes" class="footer-pillar-link"> <span>Algorithmes</span> <span class="footer-pillar-count">416</span> </a> <a href="/c/recherche-locale" class="footer-pillar-link"> <span>Recherche locale</span> <span class="footer-pillar-count">116</span> </a> </nav> </details> </div> <!-- ====== Row 3 : Dernieres prises de parole ====== --> <div class="footer-popular"> <div class="footer-popular-title">Dernieres prises de parole Google sur le SEO</div> <div class="footer-popular-grid"> <a href="/d/celui-qui-dit-tout-savoir-sur-le-seo-est-un-imposteur" class="footer-popular-link"> <span class="footer-popular-meta"> <span class="footer-popular-date">04/2026</span> <span class="footer-popular-speaker">John Mueller</span> </span> <span class="footer-popular-text">Pourquoi personne ne peut vraiment maîtriser le SEO à 100% ?</span> </a> <a href="/d/le-seo-est-complexe-multiforme-et-resilient" class="footer-popular-link"> <span class="footer-popular-meta"> <span class="footer-popular-date">04/2026</span> <span class="footer-popular-speaker">John Mueller</span> </span> <span class="footer-popular-text">Peut-on vraiment se permettre de faire n'importe quoi en SEO sans conséq…</span> </a> <a href="/d/usage-de-custom-javascript-metrics" class="footer-popular-link"> <span class="footer-popular-meta"> <span class="footer-popular-date">04/2026</span> <span class="footer-popular-speaker">Martin Splitt</span> </span> <span class="footer-popular-text">Google utilise-t-il des scripts JavaScript personnalisés pour évaluer vo…</span> </a> <a href="/d/requete-sql-dans-bigquery-pour-l-analyse-seo" class="footer-popular-link"> <span class="footer-popular-meta"> <span class="footer-popular-date">04/2026</span> <span class="footer-popular-speaker">Gary Illyes</span> </span> <span class="footer-popular-text">Faut-il vraiment maîtriser SQL et BigQuery pour faire du SEO en 2025 ?</span> </a> <a href="/d/recommandations-sur-la-taille-des-fichiers-robots-txt" class="footer-popular-link"> <span class="footer-popular-meta"> <span class="footer-popular-date">04/2026</span> <span class="footer-popular-speaker">Martin Splitt</span> </span> <span class="footer-popular-text">Faut-il vraiment respecter la limite de 100KB pour votre fichier robots.…</span> </a> <a href="/d/importancia-d-http-archive-pour-le-seo" class="footer-popular-link"> <span class="footer-popular-meta"> <span class="footer-popular-date">04/2026</span> <span class="footer-popular-speaker">Gary Illyes</span> </span> <span class="footer-popular-text">HTTP Archive : Google révèle-t-il enfin comment il analyse vraiment vos …</span> </a> <a href="/d/utilisation-de-bigquery-pour-analyser-les-sites-web" class="footer-popular-link"> <span class="footer-popular-meta"> <span class="footer-popular-date">04/2026</span> <span class="footer-popular-speaker">Martin Splitt</span> </span> <span class="footer-popular-text">BigQuery est-il vraiment indispensable pour analyser vos données SEO à g…</span> </a> <a href="/d/nouvelle-collecte-de-donnees-robots-txt-avec-http-archive" class="footer-popular-link"> <span class="footer-popular-meta"> <span class="footer-popular-date">04/2026</span> <span class="footer-popular-speaker">Gary Illyes</span> </span> <span class="footer-popular-text">Pourquoi Google publie-t-il soudainement des données massives sur l'usag…</span> </a> </div> </div> <!-- ====== Row 4 : Copyright ====== --> <div class="footer-bottom"> <span>© 2026 SEO Declarations. Tous droits reserves.</span> <span>Ce site n'est pas affilie a Google. Les declarations presentees sont issues de communications publiques de Google.</span> </div> </div> </footer> <!-- ====== Popup Newsletter (une fois par session) ====== --> <div class="nl-popup-overlay" id="nl-popup" style="display:none;" role="dialog" aria-modal="true" aria-label="Recevez une analyse complète en temps réel des dernières déclarations de Google"> <div class="nl-popup-box"> <button class="nl-popup-close" id="nl-popup-close" aria-label="Fermer">×</button> <div class="nl-popup-header"> <div class="nl-popup-badge">Restez informé</div> <h3 class="nl-popup-title">Recevez une analyse complète en temps réel des dernières déclarations de Google</h3> <p class="nl-popup-sub">Soyez le premier informé à chaque nouvelle déclaration officielle Google SEO, avec l'analyse complète incluse.</p> </div> <form class="nl-form nl-popup-form" data-lang="fr" id="nl-popup-form"> <div class="nl-popup-input-group"> <input type="email" name="email" placeholder="Votre adresse email" required autocomplete="email"> <button type="submit">S'inscrire gratuitement</button> </div> </form> <div class="nl-popup-trust"> <span class="nl-popup-trust-icon">🔒</span> <span>Aucun spam. Désinscription en 1 clic.</span> </div> </div> </div> <style> /* === Newsletter Popup === */ .nl-popup-overlay { position: fixed; inset: 0; background: rgba(15,23,42,.6); backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px); z-index: 9999; display: flex; align-items: center; justify-content: center; padding: 20px; animation: nlFadeIn .3s ease; } .nl-popup-box { background: linear-gradient(165deg, #ffffff 0%, #f8fafc 100%); border-radius: 20px; padding: 40px 36px 32px; max-width: 460px; width: 100%; position: relative; box-shadow: 0 25px 80px rgba(0,0,0,.2), 0 0 0 1px rgba(0,0,0,.05); animation: nlSlideUp .4s cubic-bezier(.16,1,.3,1); text-align: center; } .nl-popup-close { position: absolute; top: 12px; right: 14px; background: none; border: none; font-size: 22px; color: #94a3b8; cursor: pointer; line-height: 1; padding: 6px 10px; border-radius: 8px; transition: color .2s, background .2s; } .nl-popup-close:hover { color: #374151; background: #f1f5f9; } .nl-popup-header { margin-bottom: 24px; } .nl-popup-badge { display: inline-block; background: linear-gradient(135deg, #0ea5e9, #7c3aed); color: #fff; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .08em; padding: 5px 14px; border-radius: 20px; margin-bottom: 16px; } .nl-popup-title { font-size: 22px; font-weight: 800; color: #0f172a; line-height: 1.3; margin: 0 0 10px; } .nl-popup-sub { font-size: 14px; color: #64748b; line-height: 1.6; margin: 0; } .nl-popup-input-group { display: flex; gap: 0; border-radius: 12px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,.08), 0 0 0 1px rgba(0,0,0,.06); } .nl-popup-form input[type="email"] { flex: 1; padding: 14px 16px; background: #fff; color: #1e293b; border: none; font-size: 15px; outline: none; min-width: 0; } .nl-popup-form input[type="email"]::placeholder { color: #94a3b8; } .nl-popup-form button { padding: 14px 22px; background: linear-gradient(135deg, #0ea5e9, #6d28d9); color: #fff; border: none; font-size: 14px; font-weight: 700; cursor: pointer; white-space: nowrap; transition: opacity .2s; } .nl-popup-form button:hover { opacity: .9; } .nl-popup-form button:disabled { opacity: .5; cursor: default; } .nl-popup-trust { display: flex; align-items: center; justify-content: center; gap: 6px; margin-top: 14px; font-size: 12px; color: #94a3b8; } .nl-popup-trust-icon { font-size: 13px; } .nl-popup-msg { padding: 16px; border-radius: 12px; font-weight: 600; font-size: 15px; margin-top: 4px; } .nl-popup-msg.success { background: #ecfdf5; color: #059669; } .nl-popup-msg.error { background: #fef2f2; color: #dc2626; } @media (max-width: 480px) { .nl-popup-box { padding: 32px 20px 24px; } .nl-popup-input-group { flex-direction: column; border-radius: 12px; } .nl-popup-form input[type="email"] { border-bottom: 1px solid #e2e8f0; } .nl-popup-form button { padding: 14px; border-radius: 0 0 12px 12px; } .nl-popup-title { font-size: 19px; } } @keyframes nlFadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes nlSlideUp { from { transform: translateY(30px) scale(.97); opacity: 0; } to { transform: translateY(0) scale(1); opacity: 1; } } </style> <script> (function() { var LANG = 'fr'; var API_URL = 'https://seoclaims.com/api/subscribe'; var COOKIE = 'nl_subscribed'; var SESSION_KEY = 'nl_popup_shown'; var MSG = { success: 'Parfait ! Vous êtes inscrit(e).', already: 'Vous êtes déjà inscrit(e).', error: 'Une erreur est survenue, veuillez réessayer.', }; function getCookie(n) { return document.cookie.split(';').some(c => c.trim().startsWith(n + '=')); } function setCookie(n, days) { var d = new Date(); d.setDate(d.getDate() + days); document.cookie = n + '=1;expires=' + d.toUTCString() + ';path=/;SameSite=Lax'; } function closePopup(popup) { popup.style.opacity = '0'; setTimeout(function() { popup.style.display = 'none'; }, 300); sessionStorage.setItem(SESSION_KEY, '1'); } function showMsg(container, text, type) { var existing = container.querySelector('.nl-popup-msg'); if (existing) existing.remove(); var el = document.createElement('div'); el.className = 'nl-popup-msg ' + type; el.textContent = text; container.appendChild(el); } document.addEventListener('DOMContentLoaded', function() { var popup = document.getElementById('nl-popup'); var popupForm = document.getElementById('nl-popup-form'); var closeBtn = document.getElementById('nl-popup-close'); if (!popup || !popupForm) return; // Ne pas afficher si : deja inscrit OU deja vu cette session if (getCookie(COOKIE) || sessionStorage.getItem(SESSION_KEY)) return; // Afficher apres 30% scroll OU 20 secondes var nlShown = false; function showNlPopup() { if (nlShown) return; nlShown = true; popup.style.display = 'flex'; sessionStorage.setItem(SESSION_KEY, '1'); } var scrollHandler = function() { var pct = window.scrollY / (document.body.scrollHeight - window.innerHeight); if (pct > 0.3) { showNlPopup(); window.removeEventListener('scroll', scrollHandler); } }; window.addEventListener('scroll', scrollHandler, {passive: true}); setTimeout(showNlPopup, 20000); closeBtn.addEventListener('click', function() { closePopup(popup); }); popup.addEventListener('click', function(e) { if (e.target === popup) closePopup(popup); }); document.addEventListener('keydown', function(e) { if (e.key === 'Escape' && popup.style.display === 'flex') closePopup(popup); }); popupForm.addEventListener('submit', function(e) { e.preventDefault(); var email = popupForm.querySelector('input[type="email"]').value.trim(); var btn = popupForm.querySelector('button[type="submit"]'); btn.disabled = true; var origText = btn.textContent; btn.textContent = '...'; fetch(API_URL, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({email: email, lang: LANG}) }) .then(function(r) { return r.json(); }) .then(function(data) { btn.disabled = false; btn.textContent = origText; if (data.success) { setCookie(COOKIE, 365); popupForm.style.display = 'none'; showMsg(popup.querySelector('.nl-popup-box'), data.already ? MSG.already : MSG.success, 'success'); // Masquer aussi le bloc inline si present var inline = document.querySelector('.nl-inline-section'); if (inline) inline.style.display = 'none'; setTimeout(function() { closePopup(popup); }, 2500); } else { showMsg(popup.querySelector('.nl-popup-box'), data.error || MSG.error, 'error'); } }) .catch(function() { btn.disabled = false; btn.textContent = origText; showMsg(popup.querySelector('.nl-popup-box'), MSG.error, 'error'); }); }); }); })(); </script> <!-- WebSite + Organization Schema --> <script type="application/ld+json"> { "@context": "https://schema.org", "@graph": [ { "@type": "WebSite", "@id": "https://seoclaims.com/#website", "name": "SEO Declarations", "url": "https://seoclaims.com", "inLanguage": [ "fr-FR", "en" ], "publisher": { "@id": "https://seoclaims.com/#organization" }, "potentialAction": { "@type": "SearchAction", "target": { "@type": "EntryPoint", "urlTemplate": "https://seoclaims.com/?q={search_term_string}" }, "query-input": "required name=search_term_string" } }, { "@type": "Organization", "@id": "https://seoclaims.com/#organization", "name": "SEO Declarations", "url": "https://seoclaims.com", "logo": { "@type": "ImageObject", "url": "https://seoclaims.com/assets/logo.png", "width": 512, "height": 512 }, "description": "Moteur de recherche des declarations officielles de Google sur le SEO, avec analyse bilingue enrichie par IA.", "knowsAbout": [ "Search Engine Optimization", "Google Search", "Core Web Vitals", "Technical SEO", "Content SEO", "Link building", "Structured data", "AI search optimization" ], "sameAs": [] } ] }</script> <!-- ==================== CHATBOT WIDGET ==================== --> <button id="cbw-launcher" type="button" aria-label="Poser une question a l'assistant SEO" title="Poser une question a l'assistant SEO"> <svg viewBox="0 0 24 24" width="26" height="26" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path> </svg> <span class="cbw-launcher-label">Assistant SEO</span> <span class="cbw-launcher-pulse" aria-hidden="true"></span> </button> <div id="cbw-panel" role="dialog" aria-label="Assistant SEO" aria-hidden="true"> <div class="cbw-head"> <div class="cbw-head-txt"> <strong>Assistant SEO</strong> <span>Base sur les declarations officielles de Google</span> </div> <div class="cbw-head-actions"> <a href="https://seoclaims.com/chatbot" class="cbw-icon-btn" title="Ouvrir la version complete" aria-label="Ouvrir la version complete"> <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6"></path><path d="M10 14L21 3"></path><path d="M21 14v5a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5"></path></svg> </a> <button type="button" id="cbw-close" class="cbw-icon-btn" title="Fermer" aria-label="Fermer"> <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6L6 18"></path><path d="M6 6l12 12"></path></svg> </button> </div> </div> <div class="cbw-body" id="cbw-messages" aria-live="polite"> <div class="cbw-msg assistant cbw-welcome">Bonjour ! Posez-moi une question sur le SEO et Google — je reponds en citant les sources officielles.</div> </div> <form class="cbw-form" id="cbw-form" autocomplete="off"> <textarea id="cbw-input" rows="1" placeholder="Posez votre question..." maxlength="500" required></textarea> <button type="submit" id="cbw-send" aria-label="Envoyer"> <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg> </button> </form> </div> <style> #cbw-launcher { position: fixed; bottom: 24px; right: 24px; z-index: 9998; display: flex; align-items: center; gap: 8px; padding: 14px 20px 14px 16px; background: linear-gradient(135deg, #7c3aed 0%, #5b21b6 100%); color: #fff; border: none; border-radius: 999px; box-shadow: 0 8px 24px rgba(124,58,237,.35); font-family: inherit; font-size: .93rem; font-weight: 600; cursor: pointer; transition: transform .15s ease, box-shadow .15s ease; } #cbw-launcher:hover { transform: translateY(-2px); box-shadow: 0 12px 28px rgba(124,58,237,.45); } #cbw-launcher svg { flex-shrink: 0; } .cbw-launcher-label { white-space: nowrap; } .cbw-launcher-pulse { position: absolute; top: -2px; right: -2px; width: 12px; height: 12px; background: #10b981; border: 2px solid #fff; border-radius: 50%; animation: cbwPulse 2s infinite; } @keyframes cbwPulse { 0%,100% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.3); opacity: .7; } } #cbw-panel { position: fixed; bottom: 24px; right: 24px; z-index: 9999; width: 400px; max-width: calc(100vw - 32px); height: 560px; max-height: calc(100vh - 48px); background: #fff; border-radius: 16px; box-shadow: 0 20px 60px rgba(0,0,0,.2); display: grid; grid-template-rows: auto 1fr auto; overflow: hidden; transform: translateY(20px) scale(.96); opacity: 0; pointer-events: none; transition: transform .22s ease, opacity .22s ease; font-family: inherit; } #cbw-panel[aria-hidden="false"] { transform: translateY(0) scale(1); opacity: 1; pointer-events: auto; } #cbw-panel[aria-hidden="false"] ~ #cbw-launcher, body.cbw-open #cbw-launcher { opacity: 0; pointer-events: none; transform: scale(.8); } .cbw-head { display: flex; align-items: center; justify-content: space-between; padding: 14px 16px; background: linear-gradient(135deg, #7c3aed, #5b21b6); color: #fff; } .cbw-head-txt strong { display: block; font-size: .97rem; line-height: 1.2; } .cbw-head-txt span { display: block; font-size: .75rem; opacity: .85; margin-top: 2px; } .cbw-head-actions { display: flex; gap: 6px; } .cbw-icon-btn { width: 32px; height: 32px; display: inline-flex; align-items: center; justify-content: center; background: rgba(255,255,255,.12); color: #fff; border: none; border-radius: 8px; cursor: pointer; text-decoration: none; transition: background .15s; } .cbw-icon-btn:hover { background: rgba(255,255,255,.22); } .cbw-body { flex: 1; overflow-y: auto; padding: 16px; display: flex; flex-direction: column; gap: 12px; background: #fafafa; } .cbw-msg { max-width: 92%; padding: 10px 14px; border-radius: 12px; font-size: .9rem; line-height: 1.55; animation: cbwIn .2s ease-out; } @keyframes cbwIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } } .cbw-msg.user { align-self: flex-end; background: linear-gradient(135deg, #1a73e8, #1557b0); color: #fff; border-bottom-right-radius: 3px; } .cbw-msg.assistant { align-self: flex-start; background: #fff; border: 1px solid #e2e8f0; border-bottom-left-radius: 3px; color: #1e293b; } .cbw-msg.assistant.error { background: #fef2f2; border-color: #fecaca; color: #991b1b; } .cbw-msg.cbw-welcome { background: linear-gradient(135deg, #f3e8ff, #ede9fe); border-color: #c4b5fd; color: #5b21b6; } .cbw-citation { display: inline-block; background: #ede9fe; color: #5b21b6; font-size: .75rem; font-weight: 700; padding: 0 6px; border-radius: 8px; margin: 0 2px; text-decoration: none; vertical-align: 1px; } .cbw-hint { display: inline-block; background: #fef3c7; color: #92400e; font-style: normal; font-size: .82rem; padding: 1px 7px; border-radius: 6px; margin-left: 4px; font-weight: 500; } .cbw-citation:hover { background: #ddd6fe; } .cbw-sources { margin-top: 10px; padding-top: 9px; border-top: 1px dashed #cbd5e1; } .cbw-sources-title { font-size: .7rem; text-transform: uppercase; letter-spacing: .4px; font-weight: 700; color: #64748b; margin-bottom: 6px; } .cbw-src { display: block; padding: 8px 10px; background: #fff; border: 1px solid #e2e8f0; border-left: 3px solid #7c3aed; border-radius: 6px; margin-bottom: 5px; font-size: .78rem; color: inherit; text-decoration: none; transition: transform .12s; } .cbw-src:hover { transform: translateX(2px); border-color: #7c3aed; } .cbw-src strong { display: block; color: #1e293b; margin-bottom: 1px; font-size: .8rem; line-height: 1.3; } .cbw-src .cbw-src-meta { color: #64748b; font-size: .7rem; } .cbw-src .cbw-src-id { display: inline-block; background: #ede9fe; color: #5b21b6; font-family: monospace; font-size: .68rem; font-weight: 700; padding: 0 5px; border-radius: 4px; margin-right: 4px; } .cbw-typing { display: flex; gap: 4px; padding: 10px 14px; } .cbw-typing span { width: 7px; height: 7px; border-radius: 50%; background: #7c3aed; opacity: .5; animation: cbwTyping 1.2s infinite; } .cbw-typing span:nth-child(2) { animation-delay: .15s; } .cbw-typing span:nth-child(3) { animation-delay: .3s; } @keyframes cbwTyping { 0%,80%,100% { transform: scale(.7); opacity: .4; } 40% { transform: scale(1); opacity: 1; } } .cbw-form { display: flex; gap: 8px; padding: 12px; background: #fff; border-top: 1px solid #e2e8f0; } .cbw-form textarea { flex: 1; padding: 10px 12px; border: 1px solid #cbd5e1; border-radius: 10px; font-size: .9rem; font-family: inherit; resize: none; min-height: 40px; max-height: 110px; outline: none; transition: border-color .15s; } .cbw-form textarea:focus { border-color: #7c3aed; box-shadow: 0 0 0 3px rgba(124,58,237,.12); } .cbw-form button { width: 42px; height: 42px; display: inline-flex; align-items: center; justify-content: center; background: linear-gradient(135deg, #7c3aed, #5b21b6); color: #fff; border: none; border-radius: 10px; cursor: pointer; flex-shrink: 0; } .cbw-form button:disabled { opacity: .5; cursor: not-allowed; } @media (max-width: 640px) { #cbw-launcher { bottom: 16px; right: 16px; padding: 12px 16px 12px 14px; font-size: .85rem; } .cbw-launcher-label { display: none; } #cbw-launcher { padding: 14px; } #cbw-panel { top: 0; bottom: 0; left: 0; right: 0; width: 100vw; max-width: 100vw; /* dvh = dynamic viewport height : se reduit quand le clavier mobile s'ouvre */ /* fallback vh pour navigateurs tres anciens */ height: 100vh; height: 100dvh; max-height: 100vh; max-height: 100dvh; border-radius: 0; } /* Laisse le clavier rogner l'espace du body, pas de la frame entiere */ .cbw-body { overscroll-behavior: contain; } } @media print { #cbw-launcher, #cbw-panel { display: none !important; } } </style> <script> (function() { const LANG = "fr"; const T = {"sending":"...","send":"Envoyer","sources":"Sources","rate_limit":"Limite de 10 questions par heure atteinte.","error":"Erreur. Merci de reessayer.","too_short":"Question trop courte."}; const launcher = document.getElementById('cbw-launcher'); const panel = document.getElementById('cbw-panel'); const closeBtn = document.getElementById('cbw-close'); const msgsEl = document.getElementById('cbw-messages'); const formEl = document.getElementById('cbw-form'); const inputEl = document.getElementById('cbw-input'); const sendBtn = document.getElementById('cbw-send'); // Sync panel height to the visual viewport (keyboard-aware) sur mobile // Necessaire car Chrome Android n'honore pas toujours 100dvh correctement // quand le clavier virtuel s'ouvre : la panel reste a 100vh et le contenu // du haut est pousse hors ecran. function syncViewport() { if (!window.visualViewport) return; if (panel.getAttribute('aria-hidden') !== 'false') return; if (window.innerWidth > 640) { panel.style.height = ''; return; } panel.style.height = window.visualViewport.height + 'px'; } function open() { panel.setAttribute('aria-hidden', 'false'); document.body.classList.add('cbw-open'); syncViewport(); if (window.visualViewport) { window.visualViewport.addEventListener('resize', syncViewport); window.visualViewport.addEventListener('scroll', syncViewport); } // Afficher le message de bienvenue au chargement msgsEl.scrollTop = 0; setTimeout(() => inputEl.focus(), 200); } function close() { panel.setAttribute('aria-hidden', 'true'); document.body.classList.remove('cbw-open'); panel.style.height = ''; if (window.visualViewport) { window.visualViewport.removeEventListener('resize', syncViewport); window.visualViewport.removeEventListener('scroll', syncViewport); } } launcher.addEventListener('click', open); closeBtn.addEventListener('click', close); document.addEventListener('keydown', e => { if (e.key === 'Escape' && panel.getAttribute('aria-hidden') === 'false') close(); }); function esc(s) { return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); } function renderAnswer(answer, sources, queryId) { const byId = {}; (sources || []).forEach(s => { byId[s.id] = s; }); let html = esc(answer).replace(/\[#(\d+)\]/g, (m, id) => { const s = byId[id]; if (!s) return ''; return '<a class="cbw-citation" href="' + esc(s.url) + '" target="_blank" rel="noopener" data-qid="' + (queryId||0) + '" data-sid="' + id + '" data-kind="citation">#' + id + '</a>'; }); // Markdown italique *texte* -> <em>texte</em> (non greedy, non multiligne) html = html.replace(/\*([^*\n]+?)\*/g, '<em class="cbw-hint">$1</em>'); return html.replace(/\n/g, '<br>'); } function renderSources(sources, queryId) { if (!sources || !sources.length) return ''; let out = '<div class="cbw-sources"><div class="cbw-sources-title">' + esc(T.sources) + '</div>'; sources.forEach(s => { const meta = [s.speaker, s.date].filter(Boolean).join(' · '); out += '<a class="cbw-src" href="' + esc(s.url) + '" target="_blank" rel="noopener" data-qid="' + (queryId||0) + '" data-sid="' + s.id + '" data-kind="source_card">' + '<strong><span class="cbw-src-id">#' + s.id + '</span>' + esc(s.title || '') + '</strong>' + '<div class="cbw-src-meta">' + meta + '</div></a>'; }); return out + '</div>'; } // Beacon de clic : non bloquant, n'empeche pas la navigation function logClick(qid, sid, kind) { if (!qid || !sid) return; const payload = JSON.stringify({ query_id: qid, source_id: sid, kind }); try { if (navigator.sendBeacon) { navigator.sendBeacon('/api/chatbot-click', new Blob([payload], {type: 'application/json'})); } else { fetch('/api/chatbot-click', { method: 'POST', headers: {'Content-Type':'application/json'}, body: payload, keepalive: true }).catch(() => {}); } } catch (e) {} } // Delegation d'evenement sur le conteneur messages msgsEl.addEventListener('click', function(e) { const a = e.target.closest('a[data-qid][data-sid]'); if (!a) return; logClick(parseInt(a.dataset.qid, 10), parseInt(a.dataset.sid, 10), a.dataset.kind || 'source_card'); }); function addUserMsg(txt) { const d = document.createElement('div'); d.className = 'cbw-msg user'; d.innerHTML = esc(txt).replace(/\n/g, '<br>'); msgsEl.appendChild(d); scroll(); } function addBot(data, isError) { const d = document.createElement('div'); d.className = 'cbw-msg assistant' + (isError ? ' error' : ''); if (typeof data === 'string') d.innerHTML = esc(data); else { const qid = data.query_id || 0; d.innerHTML = renderAnswer(data.answer || '', data.sources || [], qid) + renderSources(data.sources || [], qid); } msgsEl.appendChild(d); // Pas de scroll : on laisse l'utilisateur sur sa question, il scrolle lui-meme pour lire la reponse } function showTyping() { const d = document.createElement('div'); d.className = 'cbw-msg assistant'; d.id = 'cbw-typing'; d.innerHTML = '<div class="cbw-typing"><span></span><span></span><span></span></div>'; msgsEl.appendChild(d); // Pas de scroll sur l'indicateur de frappe non plus } function hideTyping() { const t = document.getElementById('cbw-typing'); if (t) t.remove(); } function scroll() { msgsEl.scrollTop = msgsEl.scrollHeight; } async function ask(question, origin) { origin = origin || 'widget'; addUserMsg(question); inputEl.value = ''; inputEl.style.height = 'auto'; sendBtn.disabled = true; showTyping(); try { const resp = await fetch('/api/chatbot', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ question, lang: LANG, referer: window.location.pathname, origin }) }); hideTyping(); const data = await resp.json(); if (!resp.ok) { addBot(data.error === 'rate_limit' ? T.rate_limit : T.error, true); } else { addBot(data, false); } } catch (e) { hideTyping(); addBot(T.error, true); } finally { sendBtn.disabled = false; inputEl.focus(); } } // API publique pour declencher le chatbot depuis d'autres pages // Usage : window.ChatbotSEO.ask("Ma question", "search_fallback") window.ChatbotSEO = { open, close, ask: function(question, origin) { if (!question || question.length < 3) return; open(); // Laisser l'animation d'ouverture se lancer puis envoyer setTimeout(() => ask(question, origin || 'search_fallback'), 350); } }; inputEl.addEventListener('input', function() { this.style.height = 'auto'; this.style.height = Math.min(this.scrollHeight, 110) + 'px'; }); inputEl.addEventListener('keydown', function(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); formEl.requestSubmit(); } }); formEl.addEventListener('submit', function(e) { e.preventDefault(); const q = inputEl.value.trim(); if (q.length < 3) { addBot(T.too_short, true); return; } ask(q); }); })(); </script> <nav class="mobile-nav" aria-label="Navigation mobile"> <a href="/" class="mobile-nav-item"> <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg> <span>Recherche</span> </a> <a href="/declarations" class="mobile-nav-item"> <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg> <span>Catégories</span> </a> <a href="/?browse=1&sort=date_desc" class="mobile-nav-item"> <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg> <span>Récentes</span> </a> <a href="/en/" class="mobile-nav-item mobile-nav-lang"> <svg width="22" height="16" viewBox="0 0 640 480"> <rect width="213" height="480" fill="#00247d"/><rect x="213" width="214" height="480" fill="#fff"/><rect x="427" width="213" height="480" fill="#cf142b"/> </svg> <span>EN</span> </a> </nav> <!-- Lightbox (self-contained) --> <div id="lightbox" style="display:none;position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,.9);align-items:center;justify-content:center;padding:16px;cursor:zoom-out;" onclick="closeLightbox()"> <button onclick="closeLightbox();event.stopPropagation();" style="position:absolute;top:12px;right:16px;z-index:100000;width:44px;height:44px;border:none;background:rgba(255,255,255,.2);color:#fff;font-size:1.6rem;border-radius:50%;cursor:pointer;" aria-label="Fermer">×</button> <img id="lightbox-img" src="" alt="" style="max-width:95%;max-height:90vh;border-radius:8px;box-shadow:0 8px 40px rgba(0,0,0,.5);" onclick="event.stopPropagation()"> </div> <script> function openLightbox(src){ var lb=document.getElementById('lightbox'); document.getElementById('lightbox-img').src=src; lb.style.display='flex'; document.body.style.overflow='hidden'; } function closeLightbox(){ document.getElementById('lightbox').style.display='none'; document.body.style.overflow=''; } document.addEventListener('keydown',function(e){if(e.key==='Escape')closeLightbox();}); function ytPlay(card) { var vid = card.dataset.vid; var ts = parseInt(card.dataset.ts) || 0; card.classList.add('playing'); card.innerHTML = '<iframe src="https://www.youtube-nocookie.com/embed/' + vid + '?autoplay=1&rel=0&modestbranding=1' + (ts > 0 ? '&start=' + ts : '') + '" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>'; } </script> </body> </html>