What does Google say about SEO? /
Quick SEO Quiz

Test your SEO knowledge in 5 questions

Less than a minute. Find out how much you really know about Google search.

🕒 ~1 min 🎯 5 questions

Official statement

It is advisable to provide a rendered HTML version of JavaScript pages for all users and search engines while allowing the rest of the navigation to occur via JavaScript. This improves discoverability and indexing of pages. Angular Universal is a technology capable of handling this process.
21:16
🎥 Source video

Extracted from a Google Search Central video

⏱ 45:54 💬 EN 📅 23/02/2017 ✂ 12 statements
Watch on YouTube (21:16) →
Other statements from this video 11
  1. 1:06 La règle des trois clics est-elle vraiment morte pour le référencement ?
  2. 3:10 Faut-il vraiment éviter de combiner NoIndex et Canonical sur la même page ?
  3. 5:51 Faut-il vraiment éviter le robots.txt pour traiter le contenu dupliqué ?
  4. 6:47 Faut-il vraiment compresser ses fichiers Sitemap pour le SEO ?
  5. 8:22 Les tests A/B menacent-ils votre référencement naturel ?
  6. 12:31 Le passage HTTPS entraîne-t-il une perte de trafic organique ?
  7. 16:14 Le désaveu de liens est-il devenu totalement inutile pour le référencement ?
  8. 24:03 Pourquoi Google confond-il vos titres de pages après un passage en HTTPS ?
  9. 27:13 Pourquoi hreflang ne fonctionne pas si vos pages internationales se ressemblent trop ?
  10. 32:54 Peut-on vraiment accélérer la désindexation d'une page avec la balise noindex ?
  11. 38:15 Le ratio texte/code a-t-il vraiment un impact sur le référencement naturel ?
📅
Official statement from (9 years ago)
TL;DR

Google recommends providing a pre-rendered HTML version for all visitors, including bots, while maintaining JavaScript navigation. This hybrid approach ensures indexing and discoverability without compromising the dynamic user experience. Angular Universal is mentioned as a viable technical solution, but other frameworks offer equivalent alternatives.

What you need to understand

Why does Google emphasize server-side HTML rendering?

The Google crawler can process JavaScript, but not instantly. The discovery and indexing of pages rely on two distinct steps: the initial crawl (raw HTML) and deferred rendering (JavaScript execution in a separate queue). Several hours or even days can pass between the two.

Serving pre-rendered HTML circumvents this latency. The bot can immediately access the complete content without waiting for the rendering phase. The result: new pages are indexed faster, content changes are detected without delay, and crawl budget is preserved.

What exactly is a rendered HTML version?

A server-rendered HTML page contains the final content directly in the source code. No empty skeleton with a `

` waiting for JavaScript to wake up. The ``, `<meta>` tags, internal links, visible text, everything is present from the very first HTTP request.</p><p>Angular Universal transforms a single-page application into a <strong>isomorphic site</strong>: the Node.js server executes the application on the server, generates the complete HTML, and sends it to the browser. Then the JavaScript framework takes over for client-side navigation. The same principle applies to Next.js for React or Nuxt.js for Vue.</p><h3>Is JavaScript navigation compatible with this approach?</h3><p>Yes, and that’s the point. The <strong>first loaded page</strong> arrives as complete HTML (server-side rendering or SSR). Once the JavaScript is active, clicks on internal links trigger asynchronous requests, updating the content without a full reload. The client rendering engine takes over.</p><p>Google crawls these JavaScript transitions by following the `pushState` events and executing the handlers. However, every <strong>URL must be directly accessible</strong> in rendered HTML. If a bot visits `/product/123` without active JavaScript, it must receive the full content, not an empty shell.</p><ul><li><strong>SSR ensures immediate indexing</strong> of dynamic content without relying on the JavaScript rendering queue</li><li><strong>Client-side hydration</strong> maintains the SPA experience and interface responsiveness</li><li><strong>Each URL must respond with full HTML</strong> even without JavaScript, otherwise Googlebot indexes empty content in the event of rendering failure</li><li>The <strong>crawl budget</strong> is saved: no need to re-render pages that have already been served completely</li><li><strong>SEO metadata</strong> (title, meta description, Open Graph) is immediately visible to all bots, including social ones</li></ul></div> </div> <!-- Encart publicitaire au milieu de l'article (entre Comprendre et Avis d'expert) --> <aside class="promo-banner promo-banner-inline" data-promo-id="0" data-promo-fallback="1" style="--promo-bg: linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #312e81 100%); --promo-accent: #fbbf24;"> <div class="promo-banner-label" aria-hidden="true">Advertisement</div> <div class="promo-banner-inner"> <div class="promo-banner-text"> <span class="promo-banner-tag">Sponsored</span> <h3 class="promo-banner-headline">Looking for a trustworthy SEO agency?</h3> <p class="promo-banner-desc">Discover the best French SEO agencies, ranked, reviewed and benchmarked on real performance criteria.</p> </div> <a href="/en/top-seo-agencies-france" class="promo-banner-cta" data-promo-cta> See the comparison <span aria-hidden="true">→</span> </a> </div> </aside> <style> .promo-banner { margin: 32px auto 0; max-width: 1180px; padding: 0 16px; box-sizing: border-box; } .promo-banner-inline { margin: 36px auto; max-width: 820px; /* aligne sur la colonne d'article */ } .promo-banner-inline .promo-banner-label { font-size: .65rem; font-weight: 700; text-transform: uppercase; letter-spacing: 1.5px; color: #94a3b8; text-align: center; margin-bottom: 6px; user-select: none; } .promo-banner-inline .promo-banner-inner { padding: 22px 26px; border-radius: 14px; } .promo-banner-inline .promo-banner-headline { font-size: 1.2rem; } .promo-banner-inline .promo-banner-desc { font-size: .88rem; } .promo-banner-inline .promo-banner-image img { width: 80px; height: 80px; } .promo-banner-inner { background: var(--promo-bg); color: #fff; border-radius: 16px; padding: 28px 32px; display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 24px; box-shadow: 0 10px 30px rgba(15, 23, 42, .15), 0 1px 2px rgba(15, 23, 42, .08); position: relative; overflow: hidden; } .promo-banner-inner::before { content: ''; position: absolute; top: -50%; right: -10%; width: 350px; height: 350px; background: radial-gradient(circle, rgba(255, 255, 255, .08) 0%, transparent 60%); pointer-events: none; } .promo-banner-image { flex-shrink: 0; } .promo-banner-image img { width: 96px; height: 96px; border-radius: 12px; object-fit: cover; display: block; } .promo-banner-text { position: relative; z-index: 1; } .promo-banner-tag { display: inline-block; font-size: .68rem; font-weight: 700; text-transform: uppercase; letter-spacing: 1.2px; color: var(--promo-accent); background: rgba(255, 255, 255, .08); padding: 4px 10px; border-radius: 999px; margin-bottom: 8px; } .promo-banner-headline { font-size: 1.35rem; font-weight: 800; line-height: 1.3; margin: 0 0 6px; color: #fff; } .promo-banner-desc { font-size: .92rem; line-height: 1.55; color: rgba(255, 255, 255, .82); margin: 0; max-width: 640px; } .promo-banner-cta { display: inline-flex; align-items: center; gap: 8px; background: var(--promo-accent); color: #1e1b4b; font-weight: 700; font-size: .95rem; padding: 12px 22px; border-radius: 999px; text-decoration: none; white-space: nowrap; transition: transform .15s ease, box-shadow .15s ease, filter .15s ease; box-shadow: 0 4px 14px rgba(0, 0, 0, .18); position: relative; z-index: 1; } .promo-banner-cta:hover { transform: translateY(-2px); box-shadow: 0 8px 22px rgba(0, 0, 0, .25); filter: brightness(1.05); } .promo-banner-cta span { transition: transform .15s ease; } .promo-banner-cta:hover span { transform: translateX(3px); } @media (max-width: 768px) { .promo-banner-inner { grid-template-columns: 1fr; text-align: center; padding: 24px 20px; gap: 16px; } .promo-banner-image { justify-self: center; } .promo-banner-headline { font-size: 1.15rem; } .promo-banner-desc { font-size: .88rem; } .promo-banner-cta { justify-self: center; } } </style> <script> (function() { var banner = document.querySelector('.promo-banner[data-promo-id]'); if (!banner) return; var id = banner.getAttribute('data-promo-id'); var isFallback = banner.getAttribute('data-promo-fallback') === '1'; if (!id || isFallback) return; // pas de tracking pour le fallback (pas dans la BDD) // Impression : compter une fois quand la banniere entre dans le viewport var impressionSent = false; if ('IntersectionObserver' in window) { var io = new IntersectionObserver(function(entries) { entries.forEach(function(e) { if (e.isIntersecting && !impressionSent) { impressionSent = true; sendTrack('impression'); io.disconnect(); } }); }, { threshold: 0.5 }); io.observe(banner); } else { // Fallback : compter au chargement sendTrack('impression'); } // Click : compter au clic sur le CTA (sans bloquer la navigation) var cta = banner.querySelector('[data-promo-cta]'); if (cta) cta.addEventListener('click', function() { sendTrack('click'); }); function sendTrack(action) { try { var url = 'https://seoclaims.com/api/ad-track.php?id=' + encodeURIComponent(id) + '&a=' + action; if (navigator.sendBeacon) { navigator.sendBeacon(url); } else { fetch(url, { method: 'POST', keepalive: true }).catch(function(){}); } } catch (e) {} } })(); </script> <div class="section-card ai-section"> <h2>SEO Expert opinion</h2> <div class="content" data-field="ai_expert_opinion"><h3>Is this recommendation consistent with what we observe in the field?</h3><p>Absolutely. Pure JavaScript sites (SPA without SSR) experience documented <strong>indexing delays</strong>. We regularly observe pages discovered but not indexed for several days while Googlebot schedules their rendering. Worse: in the event of a JavaScript error (a failing dependency, server timeout), content completely disappears from the index.</p><p>Sites that have migrated to SSR report a <strong>net reduction in discovery-indexing delay</strong>. New pages appear within hours instead of several days. Content changes reflect in the SERPs without waiting for the next wave of JavaScript rendering.</p><h3>What nuances should be added to this statement?</h3><p>Google doesn’t say to abandon JavaScript; it says to serve <strong>rendered HTML for the first request</strong>. A crucial distinction: once JavaScript is loaded, navigation can remain entirely client-side. Transitions between pages do not require a full reload, as long as each URL remains directly accessible in SSR.</p><p>Another point: Angular Universal is just one solution among others. <strong>Next.js, Nuxt.js, SvelteKit</strong> do exactly the same thing. Google mentions Angular Universal out of habit (due to the Google ecosystem), but technically any framework capable of SSR is suitable. <strong>[To verify]</strong>: no public data proves Angular Universal receives preferential treatment.</p><h3>In what cases can this rule be circumvented?</h3><p>For <strong>non-indexable content</strong> (member areas, application dashboards, interactive configurators without SEO value), SSR offers no benefits. There’s no need to render a client area or internal CRM on the server. Google has nothing to index anyway.</p><p>On sites with a very low publication frequency (one update per month), the impact of rendering delay remains marginal. But as soon as you exceed a few pages per week, or touch on <strong>time-sensitive content</strong> (news, product releases, events), SSR becomes non-negotiable.</p><div class="alert">Caution: SSR requires a server infrastructure capable of executing JavaScript (typically Node.js). Pure static hosting (GitHub Pages, Netlify without server functions) is not sufficient. Prepare a Node server or a compatible CDN (Vercel, Cloudflare Workers).</div></div> </div> <div class="section-card ai-section"> <h2>Practical impact and recommendations</h2> <div class="content" data-field="ai_practical_impact"><h3>What should you do practically to implement SSR?</h3><p>Choose a <strong>SSR-compatible framework</strong> based on your technical stack. Next.js if you are on React, Nuxt.js for Vue, SvelteKit for Svelte, Angular Universal for Angular. All offer native SSR without excessive configuration. Avoid rewriting a server rendering engine from scratch; packaged solutions work well.</p><p>For infrastructure, plan for a <strong>Node.js server</strong> or a compatible serverless platform. Vercel and Netlify automatically handle SSR for Next.js and Nuxt.js. Cloudflare Workers support edge-side rendering for optimal performance. If you are self-hosting, a simple VPS with PM2 is enough to get started.</p><h3>What mistakes should you avoid when migrating to SSR?</h3><p>Do not forget to ensure that <strong>SEO metadata</strong> updates correctly on the server side. Test with `curl` or the network inspector to confirm that the title, meta description, and Open Graph tags appear in the source HTML, not just after JavaScript execution. A title managed only client-side is useless for indexing.</p><p>Another classic trap: forgetting that server-side code has <strong>no access to the DOM</strong>. References to `window`, `document`, `localStorage` will break server-side. Use conditional guards (`typeof window !== 'undefined'`) or isolate DOM-dependent code in hooks that run only client-side (useEffect for React, onMounted for Vue).</p><h3>How can I verify that my site is correctly configured?</h3><p>Inspect the <strong>raw HTML source code</strong> (right-click > View Page Source, not the DOM inspector) of a strategic page. The main content should be visible. If you see an empty `<div id="root"></div>` or a skeleton with loading spinners, SSR is not functioning.</p><p>Use <strong>Search Console</strong> and test the URL via the inspection tool. Compare the version rendered by Google with your source HTML. If Google adds content absent from the initial HTML, you are still relying on deferred JavaScript rendering. Also, check the first indexing times after publishing new pages.</p><ul class="checklist"><li>Migrate to a compatible SSR framework (Next.js, Nuxt.js, Angular Universal, SvelteKit)</li><li>Ensure that SEO metadata (title, meta, Open Graph) appears in the raw source HTML</li><li>Test each strategic URL with `curl` or View Source to confirm the presence of complete content</li><li>Remove DOM references (window, document) from server code or protect them with guards</li><li>Set up a Node.js server or a serverless platform (Vercel, Netlify, Cloudflare Workers)</li><li>Monitor discovery-indexing delays in Search Console after publishing new pages</li></ul><div class="summary">SSR radically transforms the relationship between your JavaScript site and Googlebot. You move from delayed and uncertain indexing to immediate and reliable discovery. These technical optimizations can be complex to manage alone, involving framework choice, server configuration, and metadata debugging. Engaging an SEO agency specialized in modern JavaScript architectures can help avoid costly mistakes and speed up compliance without paralyzing your product roadmap.</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">Le SSR ralentit-il le temps de chargement côté utilisateur ?</summary> <div class="faq-answer">Non, au contraire. Le SSR envoie du HTML complet dès la première requête, ce qui accélère le First Contentful Paint. Le JavaScript s'hydrate ensuite en arrière-plan sans bloquer l'affichage.</div> </details> <details class="faq-item"> <summary class="faq-question">Peut-on faire du SSR sur un site statique hébergé sur GitHub Pages ?</summary> <div class="faq-answer">Non. Le SSR nécessite un serveur capable d'exécuter du JavaScript (Node.js). GitHub Pages sert uniquement des fichiers statiques. Utilisez Vercel, Netlify ou un VPS Node.js.</div> </details> <details class="faq-item"> <summary class="faq-question">Le SSR est-il obligatoire pour tous les types de sites JavaScript ?</summary> <div class="faq-answer">Non. Les applications privées (dashboards, CRM, espaces membres) n'ont pas besoin de SSR. Seul le contenu destiné à l'indexation publique bénéficie du rendu serveur.</div> </details> <details class="faq-item"> <summary class="faq-question">Google pénalise-t-il activement les SPA sans SSR ?</summary> <div class="faq-answer">Pas directement. Mais l'indexation différée et les risques d'erreurs JavaScript créent un désavantage compétitif. Les sites SSR indexent plus vite et de manière plus fiable.</div> </details> <details class="faq-item"> <summary class="faq-question">Angular Universal est-il plus performant que Next.js pour le SEO ?</summary> <div class="faq-answer">Aucune donnée publique ne le prouve. Google cite Angular Universal par affinité d'écosystème, mais Next.js, Nuxt.js et SvelteKit offrent des capacités SSR équivalentes pour l'indexation.</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=SSR" class="seo-tag-pill">SSR</a> <a href="/en/?q=indexation" class="seo-tag-pill">indexation</a> <a href="/en/?q=crawl" class="seo-tag-pill">crawl</a> <a href="/en/?q=Angular+Universal" class="seo-tag-pill">Angular Universal</a> <a href="/en/?q=rendu+serveur" class="seo-tag-pill">rendu serveur</a> <a href="/en/?q=SPA" class="seo-tag-pill">SPA</a> <a href="/en/?q=Googlebot" class="seo-tag-pill">Googlebot</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> <a href="/en/c/pagination-structure" class="tag" style="background: #64748b20; color: #64748b;">Pagination & Structure</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">11</span> </h2> <p class="yt-same-intro"> Other SEO insights extracted from this same Google Search Central video · duration 45 min · published on 23/02/2017 </p> <div class="related-grid yt-same-grid"> <a href="/en/d/importance-d-une-architecture-de-site-accessible" class="related-item yt-same-item"> <div class="title">La règle des trois clics est-elle vraiment morte pour le référencement ?</div> <div class="meta"> <span class="yt-same-ts">⏱ 1:06</span> </div> </a> <a href="/en/d/utilisation-du-noindex-et-canonical" class="related-item yt-same-item"> <div class="title">Faut-il vraiment éviter de combiner NoIndex et Canonical sur la même page ?</div> <div class="meta"> <span class="yt-same-ts">⏱ 3:10</span> </div> </a> <a href="/en/d/robots-txt-et-gestion-du-contenu-duplique" class="related-item yt-same-item"> <div class="title">Faut-il vraiment éviter le robots.txt pour traiter le contenu dupliqué ?</div> <div class="meta"> <span class="yt-same-ts">⏱ 5:51</span> </div> </a> <a href="/en/d/compression-des-fichiers-sitemap" class="related-item yt-same-item"> <div class="title">Faut-il vraiment compresser ses fichiers Sitemap pour le SEO ?</div> <div class="meta"> <span class="yt-same-ts">⏱ 6:47</span> </div> </a> <a href="/en/d/impacts-des-tests-a-b-sur-le-seo" class="related-item yt-same-item"> <div class="title">Les tests A/B menacent-ils votre référencement naturel ?</div> <div class="meta"> <span class="yt-same-ts">⏱ 8:22</span> </div> </a> <a href="/en/d/passage-au-https-et-impacts-sur-le-trafic" class="related-item yt-same-item"> <div class="title">Le passage HTTPS entraîne-t-il une perte de trafic organique ?</div> <div class="meta"> <span class="yt-same-ts">⏱ 12:31</span> </div> </a> <a href="/en/d/desaveu-de-liens-nuisibles" class="related-item yt-same-item"> <div class="title">Le désaveu de liens est-il devenu totalement inutile pour le référencement ?</div> <div class="meta"> <span class="yt-same-ts">⏱ 16:14</span> </div> </a> <a href="/en/d/impact-des-changements-de-protocole-sur-les-titres-de-pages" class="related-item yt-same-item"> <div class="title">Pourquoi Google confond-il vos titres de pages après un passage en HTTPS ?</div> <div class="meta"> <span class="yt-same-ts">⏱ 24:03</span> </div> </a> <a href="/en/d/problemes-de-ciblage-geographique-avec-hreflang" class="related-item yt-same-item"> <div class="title">Pourquoi hreflang ne fonctionne pas si vos pages internationales se ressemblent trop ?</div> <div class="meta"> <span class="yt-same-ts">⏱ 27:13</span> </div> </a> <a href="/en/d/deindexation-de-pages-avec-no-index" class="related-item yt-same-item"> <div class="title">Peut-on vraiment accélérer la désindexation d'une page avec la balise noindex ?</div> <div class="meta"> <span class="yt-same-ts">⏱ 32:54</span> </div> </a> <a href="/en/d/ratio-texte-code-et-impact-seo" class="related-item yt-same-item"> <div class="title">Le ratio texte/code a-t-il vraiment un impact sur le référencement naturel ?</div> <div class="meta"> <span class="yt-same-ts">⏱ 38:15</span> </div> </a> </div> <a href="https://www.youtube.com/watch?v=wj9YG3WlN7w" 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/the-indexing-api-is-overwhelmed-by-bloggers" class="related-item"> <div class="title">Is Google's indexing API becoming unusable due to abuse?</div> <div class="meta"> John Mueller · May 2026 · <span class="stars">★★★</span> </div> </a> <a href="/en/d/ai-generated-websites-can-be-hard-to-recognize" class="related-item"> <div class="title">Are AI-generated websites really undetectable by Google?</div> <div class="meta"> John Mueller · May 2026 · <span class="stars">★★★</span> </div> </a> <a href="/en/d/seo-workflow-automation" class="related-item"> <div class="title">Is AI truly simplifying SEO workflows or are there critical technical risks lurking beneath the surface?</div> <div class="meta"> John Mueller · May 2026 · <span class="stars">★★</span> </div> </a> <a href="/en/d/voice-interactions-for-website-management" class="related-item"> <div class="title">Will voice management of websites revolutionize SEO?</div> <div class="meta"> John Mueller · May 2026 · <span class="stars">★</span> </div> </a> <a href="/en/d/check-the-technical-structure-to-optimize-seo" class="related-item"> <div class="title">Do AI-generated sites need a specific technical setup to rank well?</div> <div class="meta"> John Mueller · May 2026 · <span class="stars">★★★</span> </div> </a> <a href="/en/d/using-ai-for-test-automation" class="related-item"> <div class="title">How can AI automate your SEO tests without any coding?</div> <div class="meta"> Martin Splitt · May 2026 · <span class="stars">★★</span> </div> </a> </div> </div> <!-- Prev/Next --> <div class="prev-next"> <a href="/en/d/text-code-ratio-and-its-seo-impact"> <div class="nav-label">« Previous</div> <div class="nav-title">Text/Code Ratio and Its SEO Impact...</div> </a> <a href="/en/d/impact-of-external-links-on-seo" style="text-align: right;"> <div class="nav-label">Next »</div> <div class="nav-title">Impact of External Links on SEO...</div> </a> </div> <div class="back-link"> <a href="/en/" class="btn">« Back to results</a> </div> <!-- Boutons de partage sociaux (bas de page) --> <div class="share-block" data-entity-type="declaration" data-entity-id="11504" data-share-lang="en"> <div class="share-block-label"> <span class="share-icon" aria-hidden="true">🔗</span> <span>Share this article</span> </div> <div class="share-buttons"> <a class="share-btn share-btn-facebook" href="https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fseoclaims.com%2Fen%2Fd%2Fjavascript-management-for-seo" target="_blank" rel="noopener nofollow" data-network="facebook" aria-label="Share on Facebook" title="Share on Facebook"> <svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="currentColor" d="M22 12.06C22 6.5 17.52 2 12 2S2 6.5 2 12.06c0 5 3.66 9.13 8.44 9.88v-6.99H7.9v-2.89h2.54V9.85c0-2.51 1.49-3.89 3.78-3.89 1.09 0 2.24.2 2.24.2v2.46h-1.26c-1.24 0-1.63.77-1.63 1.56v1.88h2.78l-.45 2.89h-2.33v6.99C18.34 21.19 22 17.06 22 12.06z"/></svg> <span class="share-btn-text">Facebook</span> </a> <a class="share-btn share-btn-twitter" href="https://twitter.com/intent/tweet?text=According%20to%20Google%20%22Is%20it%20really%20necessary%20to%20serve%20server-rendered%20HTML%20to%20rank%20with%20JavaScript%3F%22%0A%0Ahttps%3A%2F%2Fseoclaims.com%2Fen%2Fd%2Fjavascript-management-for-seo%0A%0APlease%20RT%21" target="_blank" rel="noopener nofollow" data-network="twitter" aria-label="Share on X" title="Share on X"> <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true"><path fill="currentColor" d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg> <span class="share-btn-text">X</span> </a> <a class="share-btn share-btn-linkedin" href="https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fseoclaims.com%2Fen%2Fd%2Fjavascript-management-for-seo" target="_blank" rel="noopener nofollow" data-network="linkedin" aria-label="Share on LinkedIn" title="Share on LinkedIn"> <svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="currentColor" d="M20.45 20.45h-3.55v-5.57c0-1.33-.03-3.04-1.85-3.04-1.85 0-2.13 1.45-2.13 2.94v5.67H9.36V9h3.41v1.56h.05c.48-.9 1.64-1.85 3.38-1.85 3.61 0 4.28 2.38 4.28 5.47v6.27zM5.34 7.43a2.06 2.06 0 1 1 0-4.12 2.06 2.06 0 0 1 0 4.12zm1.78 13.02H3.56V9h3.56v11.45zM22.22 0H1.77C.79 0 0 .77 0 1.72v20.56C0 23.23.79 24 1.77 24h20.45c.98 0 1.78-.77 1.78-1.72V1.72C24 .77 23.2 0 22.22 0z"/></svg> <span class="share-btn-text">LinkedIn</span> </a> <a class="share-btn share-btn-email" href="mailto:?subject=According%20to%20Google%20%22Is%20it%20really%20necessary%20to%20serve%20server-rendered%20HTML%20to%20rank%20with%20JavaScript%3F%22&body=According%20to%20Google%20%22Is%20it%20really%20necessary%20to%20serve%20server-rendered%20HTML%20to%20rank%20with%20JavaScript%3F%22%0A%0AGoogle%20recommends%20providing%20a%20pre-rendered%20HTML%20version%20for%20all%20visitors%2C%20including%20bots%2C%20while%20maintaining%20JavaScript%20navigation.%20This%20hybrid%20approach%20ensures%20%0A%0Ahttps%3A%2F%2Fseoclaims.com%2Fen%2Fd%2Fjavascript-management-for-seo%0A%0APlease%20RT%21" data-network="email" aria-label="Share by email" title="Share by email"> <svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="currentColor" d="M2 4h20c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H2c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2zm10 7L2.4 6h19.2L12 11zm0 2L2 7v11h20V7l-10 6z"/></svg> <span class="share-btn-text">Email</span> </a> <button type="button" class="share-btn share-btn-copy" data-network="copy" data-copy-url="https://seoclaims.com/en/d/javascript-management-for-seo" data-label-copy="Copy link" data-label-copied="Link copied!" aria-label="Copy link" title="Copy link"> <svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="currentColor" d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg> <span class="share-btn-text">Copy link</span> </button> </div> </div> <script> (function(){ var blocks = document.querySelectorAll('.share-block:not([data-share-init])'); blocks.forEach(function(block){ block.setAttribute('data-share-init', '1'); var entityType = block.dataset.entityType; var entityId = block.dataset.entityId; var shareLang = block.dataset.shareLang || 'fr'; function track(network){ try { var fd = new FormData(); fd.append('entity_type', entityType); fd.append('entity_id', entityId); fd.append('network', network); fd.append('lang', shareLang); if (navigator.sendBeacon) { navigator.sendBeacon('https://seoclaims.com/api/share-track.php', fd); } else { fetch('https://seoclaims.com/api/share-track.php', { method:'POST', body: fd, keepalive: true }); } } catch(e){} } block.querySelectorAll('.share-btn').forEach(function(btn){ var network = btn.dataset.network; if (network === 'copy') { btn.addEventListener('click', function(){ var url = btn.dataset.copyUrl; var done = function(){ var txt = btn.querySelector('.share-btn-text'); var orig = btn.dataset.labelCopy; if (txt) txt.textContent = btn.dataset.labelCopied; btn.classList.add('share-btn-success'); setTimeout(function(){ if (txt) txt.textContent = orig; btn.classList.remove('share-btn-success'); }, 1800); }; if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(url).then(done).catch(function(){ window.prompt(btn.dataset.labelCopy, url); }); } else { var ta = document.createElement('textarea'); ta.value = url; ta.style.position='fixed'; ta.style.left='-9999px'; document.body.appendChild(ta); ta.select(); try { document.execCommand('copy'); done(); } catch(e){ window.prompt(btn.dataset.labelCopy, url); } document.body.removeChild(ta); } track('copy'); }); return; } btn.addEventListener('click', function(e){ track(network); if (network === 'facebook' || network === 'twitter' || network === 'linkedin') { e.preventDefault(); window.open(btn.href, 'shareWindow', 'width=620,height=620,resizable=yes,scrollbars=yes'); } // email : laisse le mailto: par defaut }); }); }); })(); </script> </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="11504"> <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/rss.xml" class="footer-link footer-link-rss" rel="alternate" type="application/rss+xml"> <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 8 8" fill="#f26522" aria-hidden="true" style="vertical-align:-2px;margin-right:6px;"> <rect width="8" height="8" rx="1.5" fill="#f26522"/> <circle cx="2" cy="6" r="1" fill="#fff"/> <path d="M1 4a3 3 0 0 1 3 3h1a4 4 0 0 0-4-4z" fill="#fff"/> <path d="M1 2a5 5 0 0 1 5 5h1a6 6 0 0 0-6-6z" fill="#fff"/> </svg>RSS feed </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">11313</span> </a> <a href="/en/c/contenu" class="footer-pillar-link"> <span>Content</span> <span class="footer-pillar-count">6385</span> </a> <a href="/en/c/nom-domaine" class="footer-pillar-link"> <span>Domain Name</span> <span class="footer-pillar-count">2828</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">7948</span> </a> <a href="/en/c/crawl-indexation" class="footer-pillar-link"> <span>Crawl & Indexing</span> <span class="footer-pillar-count">5320</span> </a> <a href="/en/c/javascript-technique" class="footer-pillar-link"> <span>JavaScript & Technical SEO</span> <span class="footer-pillar-count">3612</span> </a> <a href="/en/c/search-console" class="footer-pillar-link"> <span>Search Console</span> <span class="footer-pillar-count">2512</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/penalites-spam" class="footer-pillar-link"> <span>Penalties & Spam</span> <span class="footer-pillar-count">845</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/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/l-api-d-indexation-est-submergee-par-les-blogueurs" class="footer-popular-link"> <span class="footer-popular-meta"> <span class="footer-popular-date">May 2026</span> <span class="footer-popular-speaker">John Mueller</span> </span> <span class="footer-popular-text">L'API d'indexation Google devient-elle inutilisable à cause des abus ?</span> </a> <a href="/en/d/projets-web-simplifies-grace-au-vibe-coding" class="footer-popular-link"> <span class="footer-popular-meta"> <span class="footer-popular-date">May 2026</span> <span class="footer-popular-speaker">Martin Splitt</span> </span> <span class="footer-popular-text">Le 'vibe coding' IA peut-il vraiment accélérer vos projets web SEO ?</span> </a> <a href="/en/d/interactions-vocales-pour-la-gestion-de-sites-web" class="footer-popular-link"> <span class="footer-popular-meta"> <span class="footer-popular-date">May 2026</span> <span class="footer-popular-speaker">John Mueller</span> </span> <span class="footer-popular-text">La gestion vocale des sites web va-t-elle changer la donne pour le SEO ?</span> </a> <a href="/en/d/utilisation-de-l-ia-pour-l-automatisation-des-tests" class="footer-popular-link"> <span class="footer-popular-meta"> <span class="footer-popular-date">May 2026</span> <span class="footer-popular-speaker">Martin Splitt</span> </span> <span class="footer-popular-text">Comment l'IA peut-elle automatiser vos tests SEO sans coder ?</span> </a> <a href="/en/d/l-automatisation-des-flux-de-travail-seo" class="footer-popular-link"> <span class="footer-popular-meta"> <span class="footer-popular-date">May 2026</span> <span class="footer-popular-speaker">John Mueller</span> </span> <span class="footer-popular-text">L'IA simplifie-t-elle vraiment les workflows SEO ou masque-t-elle des ri…</span> </a> <a href="/en/d/verifier-la-structure-technique-pour-optimiser-le-seo" class="footer-popular-link"> <span class="footer-popular-meta"> <span class="footer-popular-date">May 2026</span> <span class="footer-popular-speaker">John Mueller</span> </span> <span class="footer-popular-text">Les sites générés par IA doivent-ils avoir une configuration technique p…</span> </a> <a href="/en/d/les-sites-generes-par-l-ia-peuvent-etre-difficiles-a-reconnaitre" class="footer-popular-link"> <span class="footer-popular-meta"> <span class="footer-popular-date">May 2026</span> <span class="footer-popular-speaker">John Mueller</span> </span> <span class="footer-popular-text">Les sites générés par IA sont-ils vraiment indétectables pour Google ?</span> </a> <a href="/en/d/baisse-d-indexation-sur-google-search-circulez-il-n-y-a-rien-a-voir" class="footer-popular-link"> <span class="footer-popular-meta"> <span class="footer-popular-date">May 2026</span> <span class="footer-popular-speaker">John Mueller</span> </span> <span class="footer-popular-text">La désindexation massive sur Google Search est-elle réelle ou juste un b…</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> <!-- ====== Web Push (cloche flottante + modal opt-in 3 topics) ====== --> <script> window.SEOC_PUSH = { vapidPublicKey: "BO7R09jNPfEwItp-0Yf5Q2JFcpxUIEaPrI_J8GwcXAxYdFhYrMpeYZxRk1fv1ZPOjuU1D5B9LsKs987gSDNuh5k", lang: "en", labels: {"bell":"Notifications","title":"🚀 Be the first to know what Google announces","intro":"While other SEOs find out about Google's statements <strong>weeks after publication<\/strong>, you'll know <strong>within minutes<\/strong>. Articles, Search Central videos, Labs analyses — all curated, translated and summarized for you.","fomo":"⚡ Speed = competitive edge. The SEOs who react first win.","social_proof":"⭐ Join the SEO pros who never miss a Google statement","no_spam":"✓ Zero spam, we commit","no_spam_detail":"Max 4 push\/day, never before 7am or after 22h. 1-click unsubscribe. No marketing, no partner, no third-party.","t_declarations":"📰 New Google SEO statements (Abondance)","t_videos":"🎥 New YouTube Search Central videos","t_labs":"🧪 New Labs SEO analyses","cancel":"Maybe later","confirm":"🔔 Yes, keep me informed","note":"You stay in control : choose your topics and unsubscribe anytime from the bell icon.","close":"Close","success":"✓ Notifications enabled","error":"Error, please retry","prefs_title":"Notification preferences","unsub":"Disable all","save":"Save"}}; </script> <script src="https://seoclaims.com/assets/push-public.js?v=1778774824" defer></script> <!-- ====== 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; // Sequence : push d'abord (premier ecran), newsletter ensuite (>=10s ou 30% scroll) // mais JAMAIS empilee par-dessus le modal push : on attend qu'il soit resolu. var nlShown = false; var nlReady = false; // 10s ou 30% scroll atteint var pushResolved = (typeof Notification === 'undefined') || Notification.permission !== 'default'; function showNlPopup() { if (nlShown || !nlReady || !pushResolved) return; nlShown = true; popup.style.display = 'flex'; sessionStorage.setItem(SESSION_KEY, '1'); } document.addEventListener('seoc-push-resolved', function() { pushResolved = true; showNlPopup(); }); var scrollHandler = function() { var pct = window.scrollY / (document.body.scrollHeight - window.innerHeight); if (pct > 0.3) { nlReady = true; showNlPopup(); window.removeEventListener('scroll', scrollHandler); } }; window.addEventListener('scroll', scrollHandler, {passive: true}); setTimeout(function() { nlReady = true; showNlPopup(); }, 10000); 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.","blocked":"API error, please contact an administrator.","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) { let msg = T.error; if (data.error === 'rate_limit') msg = T.rate_limit; else if (data.error === 'blocked') msg = T.blocked; addBot(msg, 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>