← Knowledge Notes
Global Growth / Knowledge note · English

Complete JavaScript SEO Guide (Basics + Diagnosis + Dynamic Rendering + Lazy Loading)

Google's 3-stage JS processing (crawl/render/index) + render queue mechanism + WRS (Web Rendering Service). 15 best practices + 11-step troubleshooting checklist. Must-avoid SPA pitfalls: soft 404 / fragment routing / Local Storage failure / cache fingerprinting / robots meta JS injection trap / web components shadow DOM. Dynamic rendering is now a workaround; SSR / static / hydration are recommended. Lazy load viewport + infinite scroll → paginated URLs. Web Components Slot must be used

Source collection:Google Search and SEO · Published here:2026-09-26 · Note updated:2026-05-17

SEOSearch EnginesOrganic growth

Google Search is the only mainstream search engine that runs JavaScript by default (using evergreen Chromium). Others (Bing has partial support, DuckDuckGo / Yandex / most niche engines)do not run JS — SSR remains the safest choice.

Core mechanism of how Google processes JS sites:WRS (Web Rendering Service) + render queue.


1. Google's 3-stage JS processing + render queue

URL
 ↓
[crawl queue] → Googlebot 取 URL → 检查 robots.txt
 ↓
fetch HTML → parse → 抽取 link 回 crawl queue
 ↓
[render queue] → WRS (headless Chromium) 跑 JS → 渲染后 HTML
 ↓
parse 渲染后 HTML → 抽 link 回 crawl queue + 用于 indexing
 ↓
[index]

Key invariants:

  • All HTTP 200 pages enter the render queue,regardless of whether they contain JS(Google makes the judgment)
  • non-200 statuses (e.g. 404) may skip rendering
  • Rendering may wait from a few seconds to longer — determined by Google's resources
  • WRS uses the current evergreen Chromium, synced with the latest Chrome
  • Resources blocked by robots.txt → will not be fetched → missing resources for rendering → Google may not see the content

Counterintuitive: the render queue is asynchronous. If you change the page via hash but the render queue is congested, what Google sees may be a snapshot from several days ago.


2. 15 JS SEO Best Practices (must-read)

Ranked by importance.

2.1 Content-related

# Practice Key points
1 Still use SSR / pre-rendering Faster for both users and crawlers,non-Google crawlers can also see content
2 unique descriptive <title> + meta description JS setup is OK, but ensure these two exist when rendering completes
3 Use <a href> instead of onclick Google only extracts links from <a href>Google only extracts links from <a href>
4 History API instead of URL fragments #/products This kind of fragment routing is unreliable for Googlebot (the AJAX crawl scheme was deprecated in 2015)
5 Add structured data use JSON-LD, JS injection is OK See Complete Overview of Structured Data (Frameworks + One-Click Lookup for 34 Rich Result Types + JSON-LD/Microdata/RDFa Selection)
6 For Web Components use <slot> to display light DOM Otherwise light DOM content will not enter the rendered HTML → will not be indexed
7 For lazy loading use viewport detection, not user scroll Google Google does not scroll your page

2.2 Error handling

# Practice Key points
8 Use accurate HTTP status codes 404 for not found / 401 for login wall / 301 for moved
9 Avoid soft 404s in SPAs Client-side routing often returns 200 for not-found → fake 200 pages get indexed. Instead use JS redirect to a real 404 URL or inject <meta name="robots" content="noindex">
10 Be careful when injecting robots meta tags via JS If Google sees noindex it may skip rendering → using JS to remove noindex is unreliable;If you want to be indexed, do not include noindex in the original HTML

2.3 Performance / caching

# Practice Key points
11 Long-lived cache + content fingerprinting WRS may ignore cache headers; use main.2bb85551.js hash filenames like this to avoid issues
12 feature detection + polyfill Detect unsupported APIs and provide fallbacks; for some (such as WebGL) Google does not support them, so SSR that portion
13 Use HTTP, do not rely on WebSockets/WebRTC Googlebot Only HTTP is sent, other connection types cannot be used

2.4 Canonical / Robots

# Practice Key points
14 rel="canonical" should be placed in HTML first JS injection is acceptable if absolutely necessary, but only one can be retained canonical; do not use JS to change it to a value different from the original HTML
15 Do not force permissions such as Camera/Geo/Notification Googlebot rejects permission requests; provide a path that works without permissions path

3. 6 Major SPA Pitfalls (single-page application specific)

Pitfall Symptom Fix
soft 404 Client-side routing returns 200 for not-found → fake error pages get indexed JS redirect to a real 404 URL, or inject <meta robots="noindex">
Fragment routing (#/products) Googlebot cannot crawl content after the fragment History API: window.history.pushState({}, '', '/products')
Local Storage / Session Storage failure Data lost across page loads WRS Local/Session Storage and Cookies are cleared on every page load.Do not rely on persistent data to serve content
Fetching data via WebSocket / WebRTC Googlebot cannot retrieve it Provide an HTTP fallback
Modifying canonical via JS Multiple conflicting canonicals → unpredictable behavior There can be only one canonical per page, check before JS injection
Fake JS paywall implementation Hiding full text in HTML + hiding it via JS Actual access restriction must be done server-side. Hiding via JS is just for show

4. 11-Step Troubleshooting Checklist

When you suspect JS is blocking indexing:

  1. Run Rich Results Test / URL Inspection Tool — check loaded resources / JS console / exceptions / rendered DOM. Use Search Console's Crawl Stats Report to monitor Googlebot + WRS activity
  2. Prevent soft 404s — see §3 item 1
  3. Expect Googlebot to reject permission requests — do not force Camera/Geo/Notification
  4. Do not use URL fragments to load different content — use the History API
  5. Do not rely on data persistence — WRS clears Local/Session/Cookie every time
  6. content fingerprinting — main.<hash>.js to prevent stale cached resources
  7. Feature detection + fallbacks / polyfills for critical APIs
  8. HTTP connections are required — do not use WebSocket / WebRTC to fetch main content
  9. Web Components render correctly — <slot> Is used? Use Rich Results Test to check if rendered HTML content is complete
  10. JS paywall implementation — full content should not be in HTML
  11. Test again after fixing — re-verify with Rich Results Test / URL Inspection. Green check means pass

Collect production JS errors: It is recommended to add a global onerror handler to log JS errors from both Googlebot and real users remotely, to locate render failure causes.

window.addEventListener('error', function(e) {
    const errorText = [
        e.message,
        'URL: ' + e.filename,
        'Line: ' + e.lineno + ', Column: ' + e.colno,
        'Stack: ' + (e.error && e.error.stack || '(no stack trace)')
    ].join('\n');
    // POST 到自己服务器
    fetch('/logError', { method: 'POST', body: errorText });
});

5. Dynamic Rendering is a Historical Workaround (not recommended in 2026)

Conclusion: do not use dynamic rendering. It has been deprecated as a recommended solution; use SSR / static rendering / hydration instead.

5.1 What it does

请求 → 检测 user-agent
  ├─ Googlebot / 不支持 JS 的 crawler → 路由到 rendering server → 返回静态 HTML
  └─ 普通浏览器 → 返回 client-rendered 版本

5.2 Why it is deprecated

  • Increases server complexity (dual-track serving)
  • High maintenance cost
  • SSR / hydration solve the same problem and are simpler
  • The real "crawler list" is hard to maintain; new bots appear that you have not adapted for

5.3 Conditions under which it does not count as cloaking

As long as the content dynamic rendering serves to both sides is similar(server-rendered version ≈ client-rendered version), Googlebot does not treat it as cloaking.

Real cloaking(violating Google SEO Basics + How Search Works + Search Essentials §7 spam policy) example: showing cats to users, showing dogs to crawlers.

5.4 Modern alternatives

Solution Applicable scenario
SSR (Server-Side Rendering) Default recommendation. Next.js / Nuxt / Astro etc.
Static rendering Content does not change often → generate HTML at build time
Hydration SSR + subsequent client-side takeover of interactivity
Pre-rendering(SSR-like) Convenient for small sites

6. Correct Lazy Loading Practices

6.1 Core principle

Lazy loading must use viewport detection, not scroll triggering. Google does not scroll your page.

6.2 3 implementation methods

Method When to use
Browser built-in loading="lazy"(<img> / <iframe>) Natively supported by modern browsers, simplest
IntersectionObserver API + polyfill Custom logic, video lazy loading
JS libraries (react-lazyload etc.) Framework integration

6.3 What not to do

  • Do not apply lazy loading to above-the-fold visible content (slow and pointless)
  • Do not rely on scroll events to trigger (Google does not scroll)

6.4 Correct implementation of infinite scroll

Infinite scroll is naturally contradictory to SEO; you need paginated:

6.5 Verification

Search Console URL Inspection Tool → Live URL → check rendered HTML

  • <img src> / <video src> Real URLs are all present → success
  • All placeholder URLs → failure, Google did not see the real images

7. Key Cheat Sheet

What you want to do How to do it Warnings
Set page title JS setup OK Ensure the title exists when rendering completes
Add structured data Inject JSON-LD via JS See Complete Overview of Structured Data (Frameworks + One-Click Lookup for 34 Rich Result Types + JSON-LD/Microdata/RDFa Selection)
Set canonical HTML preferred; JS injection is OK Only one per page, do not create conflicts
Set robots meta HTML preferred noindex may skip rendering → JS cannot modify it
Routing (SPA) History API Do not URL fragment(#/foo)
Handle 404 JS redirect to the real 404 URL, or inject noindex Client-side fake 200 responses will be indexed
Add image lazy loading viewport detection Do not trigger on scroll
Implement infinite scroll Paginated URL + History API updates Each chunk has an independent persistent URL
Detect API support feature detection + polyfill Google does not support WebGL/WebSocket and similar technologies
Fetch data HTTP fetch Do not pull main content via WebSocket/WebRTC
Cross-page persistence Server side WRS clears Local/Session/Cookie every time
Add cache control content fingerprinting WRS may ignore cache headers
Web Components <slot> display light DOM Shadow DOM only content is not indexed
Paywall Server controls full content Hiding via JS does not count as restriction

8. Relationship with other wikis

If you want to know go to
Overall Crawling Mechanism / Googlebot Details Complete Guide to Googlebot Crawling Mechanism and Indexing Control (URL/Links/Mobile-first/Meta/Testing/AMP)
Full canonical usage Complete Guide to Canonical / Redirects / Site Migration (Including Full HTTP Status Code Table + 503 Temporary Outage Playbook)
robots.txt + robots meta Complete Guide to Crawling and Indexing Control (robots.txt / noindex / X-Robots-Tag / data-nosnippet / rel)
33 types of Structured Data Complete Overview of Structured Data (Frameworks + One-Click Lookup for 34 Rich Result Types + JSON-LD/Microdata/RDFa Selection)
Full pagination practices Complete Ecommerce SEO Guide (6 Surfaces / 4 Launch Strategies / Merchant Center / 6 SD Types / 3 Pagination Modes / 13 Review Guidelines)
Search Console monitoring How to Use the Search Console + Analytics + Trends Trio (Traffic Diagnostics / Keyword Research / Looker Studio)
SEO Landscape / How Search Works Google SEO Basics + How Search Works + Search Essentials

Sources and related materials