JavaScript SEO Basics
Google can render JavaScript, but sites that depend on it can still lose pages through slow rendering, broken links, wrong status codes or accidental noindex. This guide explains how Google processes JavaScript in three phases, then gives the exact rules for links, titles, canonicals, routing, status codes and testing.
- 3 processing phases
- Crawlable link rules
- SPA status codes
- How to test
crawl -> robots.txt, fetch HTML render -> headless Chromium index -> rendered content
Google processes JavaScript sites in three phases: crawling, rendering and indexing. Use real a elements with href for links, the History API instead of #fragments for routing, meaningful HTTP status codes (or noindex on error views), and do not rely on JavaScript to remove a noindex. Test the rendered HTML in the URL Inspection tool. Server-side rendering or pre-rendering is still recommended.
How Google processes JavaScript
Googlebot fetches the URL
It checks robots.txt first. Disallowed URLs are skipped, and Google will not render JavaScript from blocked files or pages.
Pages enter a render queue
Pages returning 200 are queued unless a robots meta tag or header says not to index them. The wait can be short or longer. Headless Chromium renders the page.
Rendered HTML is indexed
Googlebot extracts links from the rendered HTML and indexes the rendered content.
Non-200 pages such as 404s may skip rendering. Google still recommends server-side rendering or pre-rendering, because it helps users and crawlers that cannot run JavaScript. For the broader picture see how Google Search works.
Links
Google discovers links from a elements with href attributes. Injecting such links with JavaScript is fine if they follow crawlable-link practices. To stop links being followed, use the nofollow mechanism.
<a href="/products/shoes">Shoes</a>
<span onclick="go('/shoes')">Shoes</span>Google’s page I used does not compare onclick handlers directly, so the risky example is my own illustration of a non-link. More in internal linking.
Routing: History API, not fragments
Do not use URL fragments (#/page) to load different content, because Googlebot cannot reliably resolve them. Use real paths in href attributes and the History API (pushState) to update the URL while navigating.
| Approach | Result |
|---|---|
| example.com/#/pricing | Unreliable for Googlebot |
| example.com/pricing with pushState | Recommended |
Titles, descriptions, canonicals
- JavaScript can set or change the title and meta description.
- Set the canonical in HTML when possible. If you use JavaScript, it must match the canonical in the original HTML. If none is in the HTML, JavaScript can add one, and Google picks it up at render time.
- Have only one canonical tag, since duplicates or conflicts produce unpredictable results.
See canonical tags and title and meta description.
Status codes and soft 404s
Use meaningful HTTP status codes where possible, such as 404 for missing pages and 401 for login-protected pages, and to signal moved pages. With client-side routing you can avoid soft 404s in two ways: redirect with JavaScript to a server URL that returns 404, or add a noindex robots meta tag to error views with JavaScript. See soft 404 errors.
Audit a JavaScript site step by step
View the rendered HTML
Open the URL Inspection tool and look at the rendered page.
Is the content present?
Check main text, links, title and canonical appear after rendering.
Check link markup
Navigation uses a with href, not scripted spans.
Check routing
No fragment-only URLs for distinct content.
Check error pages
Missing pages return a real 404, or carry noindex.
Check robots.txt
Scripts and pages must not be blocked.
Other rules from Google
| Topic | Rule |
|---|---|
| Robots meta | Changing noindex with JavaScript may not work, because Google may skip rendering when it sees noindex. Do not include noindex in the original code of pages you want indexed |
| Structured data | JSON-LD can be generated with JavaScript and injected; test it with the Rich Results Test |
| Web components | Google flattens shadow and light DOM; only visible rendered content is indexed; use slot elements |
| Lazy loading | Follow Google’s lazy-loading guidelines so content stays indexable |
| Caching | Googlebot caches aggressively and may ignore caching headers; use fingerprinted filenames |
| Browser support | Only some APIs and features are supported; use polyfills where possible, though some cannot be polyfilled |
Notes for WordPress sites
- Most themes render on the server. JavaScript SEO matters mainly for headless setups, heavy page builders or lazy-loaded content (my own note).
- Check builder output. View the rendered HTML to confirm content is not injected only after interaction.
- Do not block scripts or theme assets in robots.txt. See robots.txt syntax.
- Performance. Heavy JavaScript also hurts INP.
Troubleshooting
| Symptom | Likely cause (my own diagnosis) | Fix |
|---|---|---|
| Content missing in rendered HTML | Script blocked or fails | Check robots.txt and console errors |
| Pages not discovered | Links not real a href | Use proper anchors |
| Error pages indexed | Soft 404s in client routing | Return 404 or add noindex |
| Page stays noindex | JS removal not honoured | Remove it from the original HTML |
| Stale scripts | Aggressive caching | Fingerprint file names |
Checklist
Rendering
- Rendered HTML has content
- Scripts not blocked
- SSR or pre-render considered
- No console errors
Links and URLs
- a href navigation
- History API routing
- No fragment URLs
- Unique URL per page
Tags
- Unique title and description
- One canonical
- No JS-removed noindex
- Structured data tested
Status
- Real 404s
- Noindex on error views
- Redirects correct
- Fingerprinted assets
What Google’s page does not say
The page I used does not say how long the render queue wait is, does not state that dynamic rendering is deprecated within this text, and does not explicitly tell you never to block JavaScript or CSS, only that blocked files are not rendered.
Key terms
- Rendering
- Running a page’s JavaScript to produce final HTML.
- SSR
- Server-side rendering: HTML is built on the server.
- History API
- Browser feature to change the URL without a full reload.
- Soft 404
- An error page that returns a success status.
Common mistakes
Fragment routing
Googlebot cannot resolve it reliably.
Scripted non-links
No crawlable link.
Blocking scripts
Pages cannot render.
noindex then JS removal
May not be honoured.
Soft 404s
Error pages look valid.
Never inspecting render
You cannot see Google’s view.
Related guides
JavaScript SEO questions
Can Google index JavaScript content?
Yes. It renders pages with headless Chromium after crawling, then indexes the rendered content.
What are the three phases?
Crawling, rendering and indexing.
Do I still need server-side rendering?
Google still recommends SSR or pre-rendering because it helps users and crawlers that cannot run JavaScript.
Which links does Google follow?
a elements with href attributes.
Can I use # URLs?
No. Use the History API with real paths.
Can JavaScript remove noindex?
It may not work, since Google may skip rendering when it sees noindex.
Can I add JSON-LD with JavaScript?
Yes. Test the implementation.
How do I test?
Use the URL Inspection tool and the Rich Results Test to view rendered HTML.
The processing phases, link, routing, canonical, status code, noindex, structured data and testing rules come from Google’s page. The risky-link example, step-by-step audit, WordPress notes, troubleshooting table and checklists are my own.