What does Google say about SEO? /
Quick SEO Quiz

Test your SEO knowledge in 3 questions

Less than 30 seconds. Find out how much you really know about Google search.

🕒 ~30s 🎯 3 questions 📚 SEO Google

Official statement

If a chat modifies the page title via JavaScript, it is advised to position the chat behind a user interaction to prevent Google from indexing the modified version of the title.
3:43
🎥 Source video

Extracted from a Google Search Central video

⏱ 6:21 💬 EN 📅 16/03/2020 ✂ 10 statements
Watch on YouTube (3:43) →
Other statements from this video 9
  1. 1:48 Faut-il vraiment conserver vos anciens assets CSS et JS pour éviter les erreurs de crawl ?
  2. 2:05 Faut-il vraiment conserver les anciens assets CSS/JS pour Googlebot ?
  3. 2:40 Faut-il vraiment pré-rendre 100% du contenu pour que Googlebot l'indexe correctement ?
  4. 2:40 Le prerendering JavaScript pose-t-il encore des risques d'indexation en SEO ?
  5. 3:43 Comment éviter que JavaScript réécrive vos balises title et sabote votre indexation Google ?
  6. 4:15 Faut-il vraiment se méfier du JavaScript dans un contenu pré-rendu ?
  7. 4:35 Le JavaScript post-prerendering est-il vraiment sans danger pour le SEO ?
  8. 5:19 Faut-il vraiment privilégier le SSR et le prerendering pour améliorer son crawl ?
  9. 5:19 Le dynamic rendering va-t-il vraiment disparaître du SEO ?
📅
Official statement from (6 years ago)
TL;DR

Google can index a page title modified by JavaScript if this change occurs during the initial load. Martin Splitt recommends triggering these changes only after user interaction to maintain control over the indexed title. In practical terms: if your title changes dynamically without user action, Google may index the modified version instead of your original <title>.

What you need to understand

Why does Google sometimes index a title modified by JavaScript?

Google executes the JavaScript of pages during crawling and indexing. If a script modifies the content of the tag during page rendering — before any user interaction — the engine may interpret this dynamic version as the final title.</p><p>The issue arises when this modification was not intended for indexing. Some websites use JS to customize display based on context (geolocation, time, A/B testing) without anticipating that <strong>Googlebot will see and index</strong> this variant. The result: the title that appears in the SERPs does not match what the SEO intended.</p><h3>What does it mean to </div> </div> <div class="section-card ai-section"> <h2>SEO Expert opinion</h2> <div class="content" data-field="ai_expert_opinion"><h3>Cette recommandation est-elle cohérente avec les observations terrain ?</h3><p>Oui, et c'est même une confirmation attendue. Les tests de rendu via <strong>Search Console</strong> (outil d'inspection d'URL) montrent depuis des années que Googlebot exécute le JS et capture l'état du DOM après rendu. Si un script modifie le titre avant que ce snapshot soit pris, Google l'indexe.</p><p>Ce qui est nouveau ici, c'est la <strong>solution explicite</strong> : isoler ces modifications derrière une interaction. Martin Splitt aurait pu dire « désactivez le JS côté serveur » ou « utilisez un SSR propre », mais il propose une approche plus nuancée qui respecte les besoins UX. Reste que cette consigne suppose un audit JS minutieux — combien de sites savent précisément quel script touche au <title> et quand ?</p><h3>Quelles limites faut-il signaler ?</h3><p>Premier point : Google ne garantit <strong>jamais</strong> qu'il utilisera le <title> que vous lui fournissez. Même si vous suivez ce conseil à la lettre, le moteur peut réécrire votre titre en fonction du contexte de la requête, du contenu de la page ou de ses propres heuristiques. Cette directive limite les modifications accidentelles, pas les réécritures algorithmiques.</p><p>Deuxième point : <strong>[À vérifier]</strong> Aucune donnée publique ne quantifie l'impact réel d'un titre mal indexé sur le ranking ou le CTR. On sait que Google privilégie la cohérence sémantique entre titre, H1 et contenu, mais l'ampleur de la pénalité (si pénalité il y a) reste floue. Cette recommandation relève surtout du <strong>contrôle qualité</strong> : vous voulez maîtriser ce qui apparaît dans les SERP, point.</p><h3>Dans quels cas cette règle est-elle difficile à appliquer ?</h3><p>Les <strong>applications monopages (SPA)</strong> posent un défi particulier. Dans une SPA React ou Vue, le titre change souvent à chaque navigation client-side via un router. Techniquement, ces changements surviennent après interaction (clic sur un lien interne), donc Google ne devrait pas les indexer sur la page d'origine. Mais quid des URL pushState/replaceState ? Si Google crawle directement une de ces URLs, il verra le titre correspondant — et c'est voulu.</p><p>Autre cas épineux : les <strong>tests A/B côté client</strong>. Si vous testez des variantes de titre via JS pour mesurer le CTR organique, vous risquez que Google indexe une version non représentative. La solution ? Soit passer par un A/B serveur (mais c'est coûteux en dev), soit accepter le risque et surveiller de près les données d'indexation. Pas de solution miracle ici — c'est un arbitrage entre rigueur SEO et agilité produit.</p></div> </div> <div class="section-card ai-section"> <h2>Practical impact and recommendations</h2> <div class="content" data-field="ai_practical_impact"><h3>Que faut-il auditer en priorité sur son site ?</h3><p>Commencez par lister tous les scripts qui touchent à <strong>document.title</strong> ou à l'élément <code><title></code> du DOM. Un grep dans votre codebase sur « document.title = » ou « .textContent » appliqué à title révèle souvent des surprises : librairies analytics, chats tiers, widgets de notification.</p><p>Ensuite, testez le rendu via <strong>Search Console</strong> (Inspection d'URL > Afficher la page explorée). Comparez le titre HTML source avec celui capturé après rendu. Si les deux diffèrent et que ce n'était pas intentionnel, vous avez un candidat à corriger. Répétez l'opération sur un échantillon représentatif de templates — homepage, fiches produit, articles, landing pages.</p><h3>Comment implémenter la protection par interaction utilisateur ?</h3><p>Plutôt que d'exécuter votre script de modification de titre au <strong>load</strong> ou au <strong>DOMContentLoaded</strong>, attachez-le à un événement utilisateur. Par exemple, si vous affichez un compteur de temps passé dans le titre, déclenchez-le au premier scroll ou au premier clic dans la page.</p><p>Côté code : remplacez <code>window.addEventListener('load', modifyTitle)</code> par <code>document.addEventListener('click', modifyTitle, { once: true })</code>. Le flag <strong>{ once: true }</strong> garantit que le listener ne se déclenche qu'une fois, évitant les répétitions. Alternative : déclenchez au premier <strong>mousemove</strong> si vous avez besoin que ça se lance plus tôt, mais gardez en tête que Googlebot ne simule aucun de ces événements.</p><h3>Quelles erreurs éviter absolument ?</h3><p>Ne confondez pas « derrière interaction » avec « en différé ». Utiliser un <strong>setTimeout()</strong> de 3 secondes ne protège pas votre titre : Googlebot attend souvent plusieurs secondes pour que le JS finisse de s'exécuter, et votre script tournera quand même. Seule une interaction humaine authentique (clic, touch, keypress) est ignorée par le bot.</p><p>Autre piège : oublier les <strong>dépendances tierces</strong>. Les scripts de chat, de personnalisation, de tracking peuvent modifier le titre sans que vous le sachiez. Auditez vos tag managers et vos scripts async. Si vous ne contrôlez pas directement le code, passez par une <strong>politique CSP</strong> (Content Security Policy) pour restreindre les modifications du DOM, ou encapsulez ces scripts dans un contexte isolé.</p><ul class="checklist"><li>Lister tous les scripts qui modifient <strong>document.title</strong> (grep codebase + audit tag manager)</li><li>Tester le rendu via <strong>Search Console</strong> sur un échantillon de templates représentatifs</li><li>Remplacer les listeners <strong>load/DOMContentLoaded</strong> par des événements utilisateur (click, scroll, mousemove)</li><li>Vérifier que les scripts tiers (chat, analytics) ne modifient pas le titre au chargement</li><li>Documenter les exceptions volontaires (titres dynamiques destinés à l'indexation)</li><li>Monitorer les changements de titre indexé via les rapports de couverture et les logs de crawl</li></ul><div class="summary">Maîtriser le titre indexé par Google passe par un audit JS rigoureux et une refonte des déclencheurs de scripts. Si ces optimisations vous semblent complexes à orchestrer seul — entre audit technique, refactoring du code et coordination avec les équipes dev —, faire appel à une agence SEO spécialisée peut accélérer la mise en conformité et sécuriser votre stratégie d'indexation.</div></div> </div> <!-- FAQ --> <div class="section-card"> <h2>❓ Frequently Asked Questions</h2> <div class="faq-list"> <details class="faq-item"> <summary class="faq-question">Google indexe-t-il systématiquement le titre modifié par JavaScript ?</summary> <div class="faq-answer">Oui, si la modification survient avant la capture du rendu par Googlebot. Le moteur exécute le JS et indexe l'état du DOM après rendu, incluant tout changement de titre qui s'est produit automatiquement au chargement.</div> </details> <details class="faq-item"> <summary class="faq-question">Un setTimeout de quelques secondes suffit-il à éviter l'indexation du titre modifié ?</summary> <div class="faq-answer">Non. Googlebot attend souvent plusieurs secondes pour que le JS finisse de s'exécuter. Seule une interaction utilisateur réelle (clic, scroll) garantit que le script ne tournera pas pendant le crawl.</div> </details> <details class="faq-item"> <summary class="faq-question">Faut-il appliquer cette recommandation aux applications monopages (SPA) ?</summary> <div class="faq-answer">Dans une SPA, les changements de titre liés à la navigation client-side surviennent après interaction (clic sur lien), donc Google ne les indexera pas sur la page d'origine. En revanche, si Google crawle directement une URL pushState, il verra le titre correspondant — et c'est souvent voulu.</div> </details> <details class="faq-item"> <summary class="faq-question">Comment vérifier si Google a indexé un titre modifié par erreur ?</summary> <div class="faq-answer">Utilisez l'outil d'inspection d'URL dans Search Console et comparez le titre HTML source avec celui affiché dans l'onglet 'Page explorée'. Un écart non intentionnel signale un problème.</div> </details> <details class="faq-item"> <summary class="faq-question">Cette directive empêche-t-elle Google de réécrire mon titre dans les SERP ?</summary> <div class="faq-answer">Non. Google peut toujours réécrire votre titre selon ses propres heuristiques, indépendamment du JS. Cette recommandation limite seulement les modifications accidentelles côté client, pas les réécritures algorithmiques.</div> </details> </div> </div> <!-- Tags SEO --> <div class="seo-tags-block"> <span class="seo-tags-label">🏷 Related Topics</span> <div class="seo-tags"> <a href="/en/?q=JavaScript+SEO" class="seo-tag-pill">JavaScript SEO</a> <a href="/en/?q=indexation" class="seo-tag-pill">indexation</a> <a href="/en/?q=balise+title" class="seo-tag-pill">balise title</a> <a href="/en/?q=Googlebot" class="seo-tag-pill">Googlebot</a> <a href="/en/?q=rendu+client" class="seo-tag-pill">rendu client</a> <a href="/en/?q=interaction+utilisateur" class="seo-tag-pill">interaction utilisateur</a> <a href="/en/?q=Search+Console" class="seo-tag-pill">Search Console</a> <a href="/en/?q=SPA" class="seo-tag-pill">SPA</a> </div> </div> <!-- Categories --> <div class="declaration-tags"> <a href="/en/c/anciennete-historique" class="tag" style="background: #94a3b820; color: #94a3b8;">Domain Age & History</a> <a href="/en/c/contenu" class="tag" style="background: #10b98120; color: #10b981;">Content</a> <a href="/en/c/crawl-indexation" class="tag" style="background: #3b82f620; color: #3b82f6;">Crawl & Indexing</a> <a href="/en/c/ia-seo" class="tag" style="background: #a855f720; color: #a855f7;">AI & SEO</a> <a href="/en/c/javascript-technique" class="tag" style="background: #eab30820; color: #eab308;">JavaScript & Technical SEO</a> </div> <!-- Syntheses thematiques liees --> <!-- Autres declarations de la meme video YouTube --> <div class="section-card yt-same-video"> <h2> 🎥 From the same video <span class="yt-same-count">9</span> </h2> <p class="yt-same-intro"> Other SEO insights extracted from this same Google Search Central video · duration 6 min · published on 16/03/2020 </p> <div class="related-grid yt-same-grid"> <a href="/en/d/garder-les-anciens-assets-pour-un-meilleur-crawling" class="related-item yt-same-item"> <div class="title">Faut-il vraiment conserver vos anciens assets CSS et JS pour éviter les erreurs de crawl ?</div> <div class="meta"> <span class="yt-same-ts">⏱ 1:48</span> </div> </a> <a href="/en/d/gestion-des-actifs-obsoletes-pour-le-seo" class="related-item yt-same-item"> <div class="title">Faut-il vraiment conserver les anciens assets CSS/JS pour Googlebot ?</div> <div class="meta"> <span class="yt-same-ts">⏱ 2:05</span> </div> </a> <a href="/en/d/inclure-tout-le-contenu-lors-du-prerendering" class="related-item yt-same-item"> <div class="title">Faut-il vraiment pré-rendre 100% du contenu pour que Googlebot l'indexe correctement ?</div> <div class="meta"> <span class="yt-same-ts">⏱ 2:40</span> </div> </a> <a href="/en/d/importance-du-contenu-complet-lors-du-prerendering" class="related-item yt-same-item"> <div class="title">Le prerendering JavaScript pose-t-il encore des risques d'indexation en SEO ?</div> <div class="meta"> <span class="yt-same-ts">⏱ 2:40</span> </div> </a> <a href="/en/d/probleme-des-titres-modifies-par-javascript" class="related-item yt-same-item"> <div class="title">Comment éviter que JavaScript réécrive vos balises title et sabote votre indexation Google ?</div> <div class="meta"> <span class="yt-same-ts">⏱ 3:43</span> </div> </a> <a href="/en/d/utilisation-de-javascript-dans-le-contenu-prerendered" class="related-item yt-same-item"> <div class="title">Faut-il vraiment se méfier du JavaScript dans un contenu pré-rendu ?</div> <div class="meta"> <span class="yt-same-ts">⏱ 4:15</span> </div> </a> <a href="/en/d/usage-de-javascript-dans-les-pages-prerendered" class="related-item yt-same-item"> <div class="title">Le JavaScript post-prerendering est-il vraiment sans danger pour le SEO ?</div> <div class="meta"> <span class="yt-same-ts">⏱ 4:35</span> </div> </a> <a href="/en/d/importance-du-prerendering-et-du-rendu-cote-serveur" class="related-item yt-same-item"> <div class="title">Faut-il vraiment privilégier le SSR et le prerendering pour améliorer son crawl ?</div> <div class="meta"> <span class="yt-same-ts">⏱ 5:19</span> </div> </a> <a href="/en/d/l-avenir-du-rendering-cote-serveur" class="related-item yt-same-item"> <div class="title">Le dynamic rendering va-t-il vraiment disparaître du SEO ?</div> <div class="meta"> <span class="yt-same-ts">⏱ 5:19</span> </div> </a> </div> <a href="https://www.youtube.com/watch?v=GdCBkX5mm2U" target="_blank" rel="noopener" class="yt-same-source-link"> 🎥 Watch the full video on YouTube → </a> </div> <!-- Declarations similaires --> <div class="section-card"> <h2>Related statements</h2> <div class="related-grid"> <a href="/en/d/seo-is-complex-multifaceted-and-resilient" class="related-item"> <div class="title">Can we really afford to do anything in SEO without facing consequences?</div> <div class="meta"> John Mueller · Apr 2026 · <span class="stars">★★</span> </div> </a> <a href="/en/d/anyone-who-claims-to-know-everything-about-seo-is-a-fraud" class="related-item"> <div class="title">Why can't anyone truly master SEO 100%?</div> <div class="meta"> John Mueller · Apr 2026 · <span class="stars">★★★</span> </div> </a> <a href="/en/d/using-bigquery-to-analyze-websites" class="related-item"> <div class="title">Is BigQuery really essential for analyzing your SEO data at scale?</div> <div class="meta"> Martin Splitt · Apr 2026 · <span class="stars">★★★</span> </div> </a> <a href="/en/d/recommendations-on-the-size-of-robots-txt-files" class="related-item"> <div class="title">Should you really stick to the 100KB limit for your robots.txt file?</div> <div class="meta"> Martin Splitt · Apr 2026 · <span class="stars">★★</span> </div> </a> <a href="/en/d/use-of-custom-javascript-metrics" class="related-item"> <div class="title">Does Google use custom JavaScript scripts to evaluate your pages?</div> <div class="meta"> Martin Splitt · Apr 2026 · <span class="stars">★★★</span> </div> </a> <a href="/en/d/new-robots-txt-data-collection-with-http-archive" class="related-item"> <div class="title">Why is Google suddenly sharing massive data on robots.txt usage?</div> <div class="meta"> Gary Illyes · Apr 2026 · <span class="stars">★★★</span> </div> </a> </div> </div> <!-- Prev/Next --> <div class="prev-next"> <a href="/en/d/using-javascript-in-prerendered-content"> <div class="nav-label">« Previous</div> <div class="nav-title">Using JavaScript in Prerendered Content...</div> </a> <a href="/en/d/usage-of-javascript-in-prerendered-pages" style="text-align: right;"> <div class="nav-label">Next »</div> <div class="nav-title">Usage of JavaScript in Prerendered Pages...</div> </a> </div> <div class="back-link"> <a href="/en/" class="btn">« Back to results</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">💬 Comments <span class="comments-count">(0)</span></h3> <div class="comments-list" id="comments-list"> <p class="comments-empty" id="comments-empty">Be the first to comment.</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="8570"> <input type="hidden" name="lang" value="en"> <!-- 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">Name or alias *</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 (optional, not published)</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">Your comment *</label> <textarea id="comment-content" name="content" required maxlength="2000" rows="4"></textarea> <div class="comment-form-charcount"><span id="comment-chars">2000</span> characters remaining</div> </div> <div class="comment-form-footer"> <button type="submit" class="comment-submit-btn">Post comment</button> <span class="comment-form-notice">Comments are moderated before 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 = 'Awaiting 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">Get real-time analysis of the latest Google SEO declarations</h2> <p class="nl-inline-sub">Be the first to know every time a new official Google statement drops — with full expert analysis.</p> </div> <div class="nl-inline-action"> <form class="nl-inline-form" id="nl-inline-form" novalidate> <input type="email" name="email" placeholder="Your email address" required autocomplete="email"> <button type="submit">Subscribe for free</button> </form> <div class="nl-inline-privacy">No spam. Unsubscribe in one click.</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 = 'en'; var API_URL = 'https://seoclaims.com/api/subscribe'; var COOKIE = 'nl_subscribed'; var MSG = { success: 'You\'re in! You\'ll receive the next analyses.', already: 'You are already subscribed.', error: 'Something went wrong, please try again.', }; 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 collects, analyzes and translates <a href="/en/declarations">official Google statements</a> about search engine optimization, sourced from <a href="/en/articles">published articles</a> and <a href="/en/videos">YouTube videos</a> by Google Search Central. Each statement is enriched with <a href="/en/declarations">AI analysis</a>, classified by <a href="/en/declarations">SEO category</a> and attributed to its <a href="/en/speaker/john-mueller">author</a>. An essential tool for SEO professionals who want to know exactly what Google recommends.</p> </div> <div> <div class="footer-heading">Navigation</div> <nav class="footer-nav"> <a href="/en/declarations" class="footer-link">Statements</a> <a href="/en/labs/" class="footer-link">Labs SEO</a> <a href="/en/speaker/john-mueller" class="footer-link">Authors</a> <a href="/en/sitemap-declarations" class="footer-link">Sitemap</a> <a href="/en/top-seo-agencies-france" class="footer-link">Top SEO Agencies</a> <a href="/en/legal" class="footer-link">Legal Notice</a> </nav> </div> <div> <div class="footer-heading">Resources</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="/en/google-tools" class="footer-link" style="color: #60a5fa; font-weight: 600;">All Google Tools →</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;">Semantic</span> </span> </summary> <nav class="footer-pillar-nav"> <a href="/en/c/ia-seo" class="footer-pillar-link"> <span>AI & SEO</span> <span class="footer-pillar-count">9673</span> </a> <a href="/en/c/contenu" class="footer-pillar-link"> <span>Content</span> <span class="footer-pillar-count">5585</span> </a> <a href="/en/c/nom-domaine" class="footer-pillar-link"> <span>Domain Name</span> <span class="footer-pillar-count">1943</span> </a> <a href="/en/c/pdf-fichiers" class="footer-pillar-link"> <span>PDF & Files</span> <span class="footer-pillar-count">497</span> </a> <a href="/en/c/discover-actualites" class="footer-pillar-link"> <span>Discover & News</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;">Technical</span> </span> </summary> <nav class="footer-pillar-nav"> <a href="/en/c/anciennete-historique" class="footer-pillar-link"> <span>Domain Age & History</span> <span class="footer-pillar-count">6840</span> </a> <a href="/en/c/crawl-indexation" class="footer-pillar-link"> <span>Crawl & Indexing</span> <span class="footer-pillar-count">3560</span> </a> <a href="/en/c/javascript-technique" class="footer-pillar-link"> <span>JavaScript & Technical SEO</span> <span class="footer-pillar-count">2358</span> </a> <a href="/en/c/search-console" class="footer-pillar-link"> <span>Search Console</span> <span class="footer-pillar-count">1848</span> </a> <a href="/en/c/performance-web" class="footer-pillar-link"> <span>Web Performance</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;">Authority</span> </span> </summary> <nav class="footer-pillar-nav"> <a href="/en/c/liens-backlinks" class="footer-pillar-link"> <span>Links & Backlinks</span> <span class="footer-pillar-count">2076</span> </a> <a href="/en/c/reseaux-sociaux" class="footer-pillar-link"> <span>Social Media</span> <span class="footer-pillar-count">541</span> </a> <a href="/en/c/penalites-spam" class="footer-pillar-link"> <span>Penalties & Spam</span> <span class="footer-pillar-count">515</span> </a> <a href="/en/c/algorithmes" class="footer-pillar-link"> <span>Algorithms</span> <span class="footer-pillar-count">416</span> </a> <a href="/en/c/recherche-locale" class="footer-pillar-link"> <span>Local Search</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">Latest Google statements on SEO</div> <div class="footer-popular-grid"> <a href="/en/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">Apr 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="/en/d/le-seo-est-complexe-multiforme-et-resilient" class="footer-popular-link"> <span class="footer-popular-meta"> <span class="footer-popular-date">Apr 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="/en/d/usage-de-custom-javascript-metrics" class="footer-popular-link"> <span class="footer-popular-meta"> <span class="footer-popular-date">Apr 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="/en/d/requete-sql-dans-bigquery-pour-l-analyse-seo" class="footer-popular-link"> <span class="footer-popular-meta"> <span class="footer-popular-date">Apr 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="/en/d/recommandations-sur-la-taille-des-fichiers-robots-txt" class="footer-popular-link"> <span class="footer-popular-meta"> <span class="footer-popular-date">Apr 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="/en/d/importancia-d-http-archive-pour-le-seo" class="footer-popular-link"> <span class="footer-popular-meta"> <span class="footer-popular-date">Apr 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="/en/d/utilisation-de-bigquery-pour-analyser-les-sites-web" class="footer-popular-link"> <span class="footer-popular-meta"> <span class="footer-popular-date">Apr 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="/en/d/nouvelle-collecte-de-donnees-robots-txt-avec-http-archive" class="footer-popular-link"> <span class="footer-popular-meta"> <span class="footer-popular-date">Apr 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. All rights reserved.</span> <span>This site is not affiliated with Google. Statements presented are from public Google communications.</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="Get a complete real-time analysis of the latest Google SEO declarations"> <div class="nl-popup-box"> <button class="nl-popup-close" id="nl-popup-close" aria-label="Close">×</button> <div class="nl-popup-header"> <div class="nl-popup-badge">Stay ahead</div> <h3 class="nl-popup-title">Get a complete real-time analysis of the latest Google SEO declarations</h3> <p class="nl-popup-sub">Be the first to know every time a new official Google SEO statement drops, with full analysis included.</p> </div> <form class="nl-form nl-popup-form" data-lang="en" id="nl-popup-form"> <div class="nl-popup-input-group"> <input type="email" name="email" placeholder="Your email address" required autocomplete="email"> <button type="submit">Subscribe for free</button> </div> </form> <div class="nl-popup-trust"> <span class="nl-popup-trust-icon">🔒</span> <span>No spam. Unsubscribe in one click.</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 = 'en'; var API_URL = 'https://seoclaims.com/api/subscribe'; var COOKIE = 'nl_subscribed'; var SESSION_KEY = 'nl_popup_shown'; var MSG = { success: 'You\'re in! Check your inbox.', already: 'You are already subscribed.', error: 'Something went wrong, please try again.', }; 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": "Search engine for official Google SEO declarations, bilingual analysis and AI-enriched context.", "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="Ask the SEO assistant a question" title="Ask the SEO assistant a question"> <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">SEO Assistant</span> <span class="cbw-launcher-pulse" aria-hidden="true"></span> </button> <div id="cbw-panel" role="dialog" aria-label="SEO Assistant" aria-hidden="true"> <div class="cbw-head"> <div class="cbw-head-txt"> <strong>SEO Assistant</strong> <span>Powered by official Google declarations</span> </div> <div class="cbw-head-actions"> <a href="https://seoclaims.com/en/chatbot" class="cbw-icon-btn" title="Open full version" aria-label="Open full version"> <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="Close" aria-label="Close"> <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">Hi! Ask me anything about SEO and Google — I answer with cited sources from official declarations.</div> </div> <form class="cbw-form" id="cbw-form" autocomplete="off"> <textarea id="cbw-input" rows="1" placeholder="Ask a question..." maxlength="500" required></textarea> <button type="submit" id="cbw-send" aria-label="Send"> <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 = "en"; const T = {"sending":"...","send":"Send","sources":"Sources","rate_limit":"Limit of 10 questions per hour reached.","error":"Error. Please try again.","too_short":"Question too short."}; 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="Mobile navigation"> <a href="/en/" 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>Search</span> </a> <a href="/en/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>Categories</span> </a> <a href="/en/?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>Recent</span> </a> <a href="/" class="mobile-nav-item mobile-nav-lang"> <svg width="22" height="16" viewBox="0 0 640 480"> <rect width="213" height="480" fill="#002654"/><rect x="213" width="214" height="480" fill="#fff"/><rect x="427" width="213" height="480" fill="#ce1126"/> </svg> <span>FR</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>