ARTICLE
JavaScript SEO: When Google Can't See Your Content
How client-side rendering breaks indexing, what two-wave crawling means in practice, and how to verify Google sees your content.
Jun 20, 20267 min read
An agency inherits a React SPA. Rankings have been declining for six months. Google Search Console shows the pages being crawled and indexed. The client's developer says "the content is all there." The agency checks the live URL in a browser and everything looks fine.
Then they check view-source and find a <div id="root"></div> with nothing inside it.
This is JavaScript SEO failure — and it's the category most agencies miss because it's invisible to a normal browser check.
The Two-Wave Indexing Problem
Google's crawler operates in two waves for JavaScript-heavy pages:
Wave 1: Googlebot fetches the raw HTML. If the content is in the HTML, it's indexed immediately. If the page requires JavaScript to render content, Googlebot queues it.
Wave 2: Some time later — hours, days, sometimes weeks — Google sends the page through its Web Rendering Service (WRS), which executes JavaScript and re-indexes the rendered content.
The problems with Wave 2:
- Delay: Pages that require rendering are indexed later than pages with server-rendered HTML. For competitive queries, this delay costs rankings.
- Inconsistency: Google does not guarantee rendering all pages or re-rendering on schedule. Low-priority pages (thin content, few inbound links) may stay in Wave 1 only.
- JavaScript budget: Googlebot has a resource budget per crawl. Heavy SPAs that load dozens of JavaScript bundles may not fully render within budget, producing partial indexing.
- AI crawlers don't execute JavaScript at all — more on this below.
How to Check Whether Google Sees Your Content
The browser is the wrong tool for this check. The browser always executes JavaScript. You need to see what Googlebot sees before rendering.
Method 1: View page source
Right-click → "View Page Source" shows the raw HTML as served by the server. If your <h1>, body copy, and product descriptions aren't in that source, they're not in Wave 1 indexing.
# From the command line
curl -A "Mozilla/5.0" https://example.com/product-page | grep -i "your expected content"If the content doesn't appear in the curl output, it's client-side rendered.
Method 2: Google Search Console URL Inspection
Use "Test Live URL" and then "View Tested Page" → "Screenshot" and "HTML." The HTML tab shows what Googlebot saw after rendering. Compare it to view-source. Missing content in the raw HTML that appears in the GSC screenshot confirms you're dependent on Wave 2 rendering.
Method 3: Meta Tag Analyzer
Run the Meta Tag Analyzer on the page. If the tool reports missing titles, descriptions, or canonical tags on a page that appears to have them in the browser, those tags are being injected by JavaScript after initial load — Googlebot may or may not pick them up.
Client-Side Rendering Failure Patterns
Links only in JavaScript
Navigation built entirely in JavaScript is one of the most common crawl issues in SPAs:
<!-- INVISIBLE TO GOOGLEBOT (wave 1) -->
<a onclick="navigate('/about')">About</a>
<!-- CRAWLABLE -->
<a href="/about">About</a>JavaScript onclick handlers that change routes without an href attribute are invisible in Wave 1. Googlebot cannot follow links that have no href. Internal links built this way mean entire sections of the site may not be crawled at all.
Meta tags injected by JS
React Helmet and similar libraries inject <title> and <meta> tags after the JavaScript runtime initializes. The raw HTML has no meaningful title tag:
<!-- Raw HTML (what Googlebot sees in Wave 1) -->
<head>
<title>React App</title>
<!-- No description, no og:title, no canonical -->
</head><!-- Rendered HTML (what the browser shows) -->
<head>
<title>Best Running Shoes 2026 | Pace Athletics</title>
<meta name="description" content="Expert-picked running shoes..." />
<link rel="canonical" href="https://paceathletics.com/running-shoes" />
</head>Wave 1 indexes "React App" as the title. Wave 2 may correct this. Search Console may show the correct title. The page may still underperform because the delay put a competitor ahead.
Hash-based routing
Old-style SPAs using hash URLs (example.com/#/products/shoes) are a complete indexing dead end. Hash fragments are never sent to the server and are ignored by most crawlers. Google has improved hash routing support over time but it remains unreliable. If you find a client on hash routing, this is a migration project, not a tag fix.
Hydration errors
In frameworks like Next.js or Nuxt, the server renders HTML (SSR) and then React "hydrates" it on the client. If there's a mismatch between what the server rendered and what React tries to render (different data, different components), React throws a hydration error and replaces the server-rendered content with a blank state.
Result: the raw HTML has the correct content, the browser ends up showing an error state, and users bounce. This is both a conversion problem and an indexing problem if the error state persists.
Check the browser console on key pages. Hydration errors appear as:
Warning: Text content did not match. Server: "..." Client: "..."
The Fix Hierarchy
In order of preference:
1. Static Site Generation (SSG)
For pages where content doesn't change per-user (blog posts, product pages, location pages), pre-render to static HTML at build time. Zero JavaScript required for indexing. Fastest possible page speed. No crawl budget spent on rendering.
Next.js: export const dynamic = 'force-static' on the route.
2. Server-Side Rendering (SSR)
For pages where content changes per-request but is not user-specific (e.g., inventory pages, event listings), render on the server and send complete HTML. The user's browser gets the full page; so does Googlebot.
Next.js App Router defaults to SSR for Server Components — this is one of the biggest SEO benefits of moving from pages/ to app/.
3. Prerendering / Snapshot Services
For legacy SPAs that can't be migrated to SSR/SSG, a prerender service (Prerender.io, Rendertron) detects crawler user agents and serves a pre-rendered HTML snapshot instead. It's a workaround, not a fix — the snapshots need to stay current and the infrastructure adds complexity.
4. Leave it as CSR (last resort)
For authenticated pages behind login, client-side rendering is fine — those pages aren't indexed anyway. CSR is appropriate for dashboards, user settings, and any content that's personalized and not meant for search.
AI Crawlers Don't Execute JavaScript
This is the big GEO wrinkle that most JS SEO articles miss. The AI platforms — Perplexity, Anthropic's ClaudeBot, OpenAI's GPTBot — send HTTP crawlers that fetch raw HTML only. They do not execute JavaScript. They do not wait for Wave 2.
If a site's content exists only in client-side JavaScript, it is invisible to every AI search platform. Content that's not in the raw HTML will never be cited in ChatGPT, Perplexity, or Claude responses.
This means the stakes of JavaScript SEO are higher in 2026 than they were in 2020. A site that was "working fine" with client-side rendering for Google (because Google eventually renders it) is failing for every AI platform that might cite it.
The fix is the same: SSR or SSG. But the urgency is higher.
What to Skip
Don't implement prerendering as a long-term solution for new sites. If you're starting fresh or have the ability to migrate, SSR or SSG is the right answer. Prerendering is debt.
Don't assume Next.js means SSR. Client Components in Next.js App Router render on the client. If your entire app is "use client", you've built a CSR app with a Next.js wrapper. Check which components are actually server-rendered.
Don't use <noscript> tags to "fix" JavaScript SEO. Google ignores <noscript> for crawling purposes. It's useful for accessibility and analytics fallback, not for indexing hidden content.
Don't test JavaScript SEO issues only in Chrome DevTools. Chrome's Lighthouse and DevTools both execute JavaScript. Your test environment must match what Googlebot sees: raw HTTP response, no JS execution.
The audit one-liner for JavaScript SEO: View page source on every key page — if the content, titles, and links aren't in the raw HTML, they're invisible to AI crawlers and unreliably indexed by Google.
Related reading
Keep reading
Schema Validation: Catching Errors That Block Rich Results
Why valid JSON-LD still fails rich results, and how to use a schema markup validator to catch every blocking error.
How to Fix Redirect Chains and Loops
Redirect chains waste crawl budget, dilute link equity, and slow page load. Here's how to find them, collapse them, and prevent them from coming back.
SSL Certificate Errors That Quietly Cost You Rankings
Expired, mismatched, or misconfigured SSL certificates leak link equity and trigger ranking drops. Here's how to catch and fix every cert failure.