Google Search is the only major search engine that runs JavaScript by default (using evergreen Chromium). Other search engines (Bing has partial support, DuckDuckGo / Yandex / most niche engines)do not run JS — SSR (Server-Side Rendering) remains the safest option.
Core mechanism Google uses to process JavaScript sites:WRS (Web Rendering Service, Web Page Rendering Service) + render queue.
1. Google's 3-stage JavaScript processing + render queue
URL
↓
[crawl queue 抓取队列] → Googlebot 取 URL → 检查 robots.txt
↓
fetch HTML → parse 解析 → 抽取链接回 crawl queue
↓
[render queue 渲染队列] → WRS(无头 Chromium)跑 JS → 渲染后 HTML
↓
parse 解析渲染后的 HTML → 抽链接回 crawl queue + 用于 indexing 索引
↓
[index 索引]
Key invariants:
- All HTTP
200pages enter the render queue,regardless of whether they contain JS(as determined by Google itself) - Non-200 status codes (e.g. 404) may skip rendering
- Rendering may wait from a few seconds to much longer — depending on Google's available resources
- WRS uses the current evergreen Chromium, kept in sync 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 changes but the render queue is congested, Google may see a snapshot from several days ago.
2. 15 JavaScript SEO Best Practices (Must Read)
Ordered by importance.
2.1 Content-related
| # | Practice | Key points |
|---|---|---|
| 1 | Still use SSR (Server-Side Rendering) / prerendering | Faster for both users and crawlers,non-Google crawlers can also see content |
| 2 | Unique, descriptive <title> + meta description |
Setting them via JS is acceptable, but ensure both exist when rendering completes |
| 3 | Use <a href> instead of onclick |
Google only extracts links from <a href> elements |
| 4 | Use the History API instead of URL fragments | #/products Fragment-based routing like this is unreliable for Googlebot (the AJAX crawl scheme was deprecated in 2015) |
| 5 | Add structured data using JSON-LD; injecting via JS is also acceptable | See Complete Structured Data Overview (Frameworks + Quick Reference for 34 Rich Result Types + JSON-LD/Microdata/RDFa Selection Guide) |
| 6 | For Web Components, use <slot><slot> to display light DOM |
Otherwise light DOM content will not appear in the rendered HTML → will not be indexed |
| 7 | Use viewport detection for lazy loading, not user scroll events | 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 walls / 301 for moved content |
| 9 | SPAs must avoid soft 404 errors | Client-side routing often returns 200 for not-found pages → fake 200 responses get indexed. Instead, use JS to 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 → JS attempts to remove noindex are unreliable;if you want the page indexed, do not include noindex in the original HTML |
2.3 Performance / Caching
| # | Practice | Key points |
|---|---|---|
| 11 | Long-lived caching + content fingerprinting | WRS may ignore cache headers; use main.2bb85551.js hash-based filenames to avoid issues |
| 12 | Feature detection + polyfills | Detect unsupported APIs and provide fallbacks; for some features (e.g. WebGL) that Google does not support, render that portion via SSR |
| 13 | Use HTTP, do not rely on WebSockets / WebRTC | Googlebot Only HTTP requests are sent, other connection types cannot be used |
2.4 Canonicalization / Robots
| # | Practice | Key points |
|---|---|---|
| 14 | rel="canonical" Place canonical tags in HTML first |
If you must inject via JS it is acceptable, but only one canonical can be retained; do not use JS to change the canonical to a different value than the original HTML |
| 15 | Do not force permission requests for Camera / Geo / Notification etc. | Googlebot rejects permission requests; provide a fallback that works without permissions path |
3. 6 Major SPA Pitfalls
| Pitfall | Symptom | Fix |
|---|---|---|
| Soft 404 | Client-side routing returns 200 for not-found pages → fake error pages get indexed |
Use JS to 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 failures | Data lost across page loads | WRS WRS clears Local / Session Storage and Cookies on every page load.Do not rely on persistent data to serve content |
| Fetching data via WebSocket / WebRTC | Googlebot cannot retrieve the data | Provide an HTTP fallback |
| Modifying canonical via JS | Conflicting multiple canonicals → unpredictable behavior | A single page can only have one canonical; check before injecting via JS |
| Fake JS paywall implementation | Full content hidden in HTML + hidden via JS | Actual access restriction must be done server-side. Hiding via JS is only cosmetic |
4. 11-Step Diagnosis Checklist
When you suspect JavaScript is blocking indexing:
- Run Rich Results Test / URL Inspection Tool — check loaded resources / JS console / errors / post-render DOM. Use Search Console's Crawl Stats Report(Crawl Stats report) to monitor Googlebot + WRS activity
- Prevent soft 404s — see §3 item 1
- Expect Googlebot to reject permission requests — do not force Camera / Geo / Notification permissions
- Do not use URL fragments to load different content — use the History API
- Do not rely on data persistence — WRS clears Local / Session / Cookie on every load
- Use content fingerprinting —
main.<hash>.jsto prevent caching of stale resources - Feature detection + fallbacks / polyfills for critical APIs
- HTTP connections are required — do not use WebSocket / WebRTC to fetch main content
- Ensure Web Components render correctly —
<slot>Is <slot> used? Use the Rich Results Test to check whether the post-render HTML content is complete - Check JS paywall implementation — full content should not be present in the HTML
- Test again after fixes — re-verify with Rich Results Test / URL Inspection. Pass with a green checkmark
Collect JS errors from production: It is recommended to set up a global onerror handler to log JS errors from both Googlebot and real users remotely, to identify causes of rendering failures.
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 Legacy Workaround (Not Recommended as of 2026)
Conclusion: do not use dynamic rendering. It has been downgraded from recommended status; use SSR / static rendering / hydration instead.
5.1 What it does
请求 → 检测 user-agent
├─ Googlebot / 不支持 JS 的抓取器 → 路由到 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 to
5.3 Conditions under which it does not count as cloaking
As long as dynamic rendering serves similar content to both sides (server-rendered version ≈ client-rendered version), Googlebot does not treat it as cloaking.
Genuine cloaking(violating Google SEO Basics + How Search Works + Search Essentials §7 spam policies) example: showing cats to users, showing dogs to crawlers.
5.4 Modern alternatives
| Solution | Use case |
|---|---|
| SSR (Server-Side Rendering) | Recommended by default. Next.js / Nuxt / Astro etc. |
| Static rendering | Content that does not change often → generate HTML at build time |
| Hydration | SSR + subsequent client-side takeover of interactivity |
| Prerendering (SSR-like) | Convenient for small sites |
6. Correct Lazy Loading Practices
6.1 Core principle
Lazy loading must use viewport detection, not scroll event triggers. Google does not scroll your page.
6.2 3 implementation methods
| Method | When to use |
|---|---|
Native browser lazy loading loading="lazy"(<img> / <iframe>) |
Natively supported in modern browsers, simplest option |
| IntersectionObserver API + polyfill | Custom logic, video lazy loading |
| JS libraries (react-lazyload etc.) | Framework integration |
6.3 What to avoid
- Do not apply lazy loading to above-the-fold visible content (slow and pointless)
- Do not rely on scroll event triggers (Google does not scroll)
6.4 Correct implementation of Infinite scroll
Infinite scroll is inherently at odds with SEO; it must be paginated:
- Each chunk must have a persistent unique URL(
/articles?page=12) - The same URL must load consistent content every time(avoid
?date=yesterdaysuch relative parameters) - Link page URLs sequentially (so crawlers can discover them)
- When the user scrolls to a new chunk,update the URL via the History API(so users can refresh / share / link)
- See Complete Ecommerce SEO Guide (6 Surfaces / 4 Launch Strategies / Merchant Center / 6 SD Types / 3 Pagination Patterns / 13 Review Guidelines) pagination chapter
6.5 Verification
In Search Console, go to URL Inspection Tool → Live URL → View the rendered HTML:
<img src>/<video src>All real URLs are present → Success- All are placeholder URLs → Failure; Google did not see the actual images
7. Key Quick Reference Cheat Sheet
| What you want to do | How to do it | Warnings |
|---|---|---|
| Set page title | Setting via JS is OK | Make sure the title exists when rendering completes |
| Add structured data | Inject JSON-LD via JS | See Complete Structured Data Overview (Framework + One-Click Lookup for 34 Rich Result Types + JSON-LD/Microdata/RDFa Selection Guide) |
| Set canonical | Prefer HTML; JS injection is OK | Only one per page, do not create conflicts |
| Set robots meta | Prefer HTML | noindex Rendering may be skipped → cannot be changed via JS |
| Routing (SPA) | History API | Do not URL fragment(#/foo) |
| Handle 404 | Redirect via JS 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 URLs + History API updates | Each chunk must have its own independent persistent URL |
| Detect API support | Feature detection + polyfill | Google does not support WebGL / WebSocket, etc. |
| Fetch data | HTTP fetch | Do not pull main content via WebSocket / WebRTC |
| Cross-page persistence | Server-side | WRS clears Local Storage / Session Storage / Cookies on every run |
| Add cache control | Content fingerprinting | WRS may ignore cache headers |
| Web Components | <slot>Use <slot> to display light DOM |
Shadow DOM only content is not indexed |
| Paywall | Control full content on the server side | Hiding via JS does not count as access restriction |
8. Relationship to Other Wiki Pages
Sources and Related Materials
- https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics?hl=en
- https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics?hl=zh-cn
- https://developers.google.com/search/docs/crawling-indexing/javascript/dynamic-rendering?hl=en
- https://developers.google.com/search/docs/crawling-indexing/javascript/dynamic-rendering?hl=zh-cn
- https://developers.google.com/search/docs/crawling-indexing/javascript/fix-search-javascript?hl=en
- https://developers.google.com/search/docs/crawling-indexing/javascript/fix-search-javascript?hl=zh-cn
- https://developers.google.com/search/docs/crawling-indexing/javascript/lazy-loading?hl=en
- https://developers.google.com/search/docs/crawling-indexing/javascript/lazy-loading?hl=zh-cn
- Google SEO Starter Guide + How Search Works + Search Essentials
- Complete Guide to Googlebot Crawling Mechanism and Indexing Control (URL/Links/Mobile-first/Meta/Testing/AMP)
- Complete Guide to Canonical / Redirects / Site Migration (Including Full HTTP Status Code Table + 503 Temporary Outage Playbook)
- Complete Guide to Crawling and Indexing Control (robots.txt / noindex / X-Robots-Tag / data-nosnippet / rel)