JavaScript powers most of the modern web, but it creates a fundamental tension with search engines. Crawlers are built to read HTML. When your content only exists after JavaScript runs, a crawler that can’t or won’t execute that JavaScript sees an empty page. This guide covers how Googlebot actually processes JavaScript, where things go wrong, and the rendering strategies that fix it.
How Googlebot processes JavaScript
Googlebot handles JavaScript in two separate phases. In the first phase it crawls your URL and receives the initial HTML response. In the second phase it queues that page for rendering: a headless Chromium process executes the JavaScript and produces the final DOM. That second phase is deferred, and the queue can be days behind crawling.
The gap matters. Between the crawl and the render, Googlebot has indexed your initial HTML. If that HTML is near-empty (a <div id="root"></div> and a bundle reference), your content may be absent from the index until rendering catches up. And if Googlebot’s renderer never gets around to a page because of budget constraints or because a critical JS resource is blocked, the content may never index at all.
Key facts about Googlebot’s JS rendering:
- Googlebot uses an evergreen Chromium-based renderer, so it can execute most modern JavaScript.
- Rendering is not instant. There is a documented delay between when a page is crawled and when its JavaScript is rendered.
- Resources blocked in robots.txt cannot be fetched. If your main JS bundle is blocked, the page renders as empty HTML.
- JavaScript that fires only on user interaction (click, scroll, hover) is generally not triggered by the renderer.
Client-side rendering: why it hurts
Client-side rendering (CSR) means the server sends a minimal HTML shell and the browser runs JavaScript to build the page. The initial HTML response contains almost no content.
For SEO, the practical effect is that Googlebot’s first-pass crawl sees nothing useful: no headings, no body copy, no product descriptions, no structured data. The rendered version may be correct, but getting there requires the deferred render queue to process the page, which isn’t guaranteed or fast.
CSR also makes it hard to serve unique <title> and <meta description> tags per page without extra tooling, since those tags are typically set by the same JavaScript that renders the content.
SSR, SSG, prerendering, and dynamic rendering compared
There are four main approaches for getting content into the initial HTML response. Each makes a different trade-off.
| Approach | What gets sent | Best for |
|---|---|---|
| CSR (client-side rendering) | Empty HTML shell | Web apps where SEO doesn’t matter |
| SSR (server-side rendering) | Fully rendered HTML per request | Dynamic content, personalised pages, frequently updated data |
| SSG (static site generation) | Pre-built HTML at build time | Content that changes infrequently (blogs, docs, marketing sites) |
| Prerendering / dynamic rendering | Static HTML served to crawlers, JS version served to browsers | Legacy CSR apps where a full SSR migration isn’t practical |
SSR generates HTML on the server for each request. When Googlebot fetches the URL, it gets the full page content immediately, no rendering queue required. The trade-off is server load and response latency.
SSG pre-builds all pages at deploy time. The server returns static HTML instantly. This is the fastest and most crawler-friendly approach for content sites. Frameworks like Next.js, Astro, and Gatsby all support SSG.
Prerendering (sometimes called dynamic rendering) detects crawler user agents and serves a pre-rendered static version, while real users get the full JS experience. Google has acknowledged this as a workaround but notes it can be complex to maintain and that the preferred solution is SSR or SSG.
For sites built specifically for SEO, SSG is usually the right choice. For single-page applications that need dynamic content, SSR is the better path.
Hydration
SSR and SSG both involve a process called hydration. The server sends fully rendered HTML so crawlers and users see content immediately. Then the browser downloads the JavaScript bundle and “hydrates” the static HTML by attaching event listeners and making it interactive.
For SEO, hydration is transparent: Googlebot indexes the server-rendered HTML and doesn’t need to wait for hydration. The risk is hydration mismatches, where the client-rendered output differs from what the server sent. These don’t directly hurt crawling but can cause visible page errors.
The most common JavaScript SEO failures
Content that only appears after JavaScript runs
If a product description, heading, or paragraph is injected by JavaScript after the initial page load, it may not exist in the initial HTML Googlebot indexes. Test this by viewing the raw page source (not DevTools, which shows the rendered DOM). If your content isn’t there, it’s client-side rendered.
Navigation built on click handlers instead of real links
This is one of the most damaging patterns. A navigation element that uses a JavaScript click handler to load a new view looks identical to users but is invisible to crawlers.
<!-- Not crawlable: Googlebot won't follow this -->
<button onclick="loadPage('/about')">About</button>
<!-- Crawlable: real anchor tag with an href -->
<a href="/about/">About</a>
Googlebot discovers new pages by following <a href> links. If your navigation, pagination, or internal links use click handlers, JavaScript redirects, or history.pushState without a real anchor, Googlebot may never find those pages. This is especially common in React applications and other component-based frameworks where router navigation is handled programmatically.
Blocked JavaScript resources in robots.txt
If your robots.txt disallows access to your JavaScript bundle or CSS files, Googlebot’s renderer cannot fetch those resources. The result is a partially or completely empty render. Check Google Search Console’s URL Inspection tool to see which resources are blocked.
A common mistake is blocking /static/ or /_next/ paths in robots.txt, which prevents Googlebot from loading the assets it needs to render the page.
Lazy-loaded content crawlers never trigger
Infinite scroll, load-more buttons, and content that loads on scroll are not triggered by crawlers. Googlebot doesn’t scroll. If your main content or pagination is behind one of these patterns, it won’t be indexed. Use static pagination with real URLs (/blog/page/2/) or ensure content is present in the initial HTML response.
Diagnostic workflow
Use these three checks in sequence to identify JavaScript SEO problems on any page.
1. View raw source. In your browser, right-click the page and choose “View Page Source” (not Inspect). You’re looking at the exact HTML the server sent before any JavaScript ran. If your key content, headings, and meta tags are absent here, you have a CSR problem.
2. Search Console URL Inspection. Open Google Search Console, paste your URL into the inspection tool, and click “Test Live URL.” The “More Info” tab shows you exactly what Googlebot rendered, which resources were blocked, and whether the page was indexable. Compare the rendered screenshot to what a user sees.
3. Fetch with JavaScript disabled. In Chrome DevTools, open the Command Palette (Cmd+Shift+P or Ctrl+Shift+P), search for “Disable JavaScript,” and reload the page. What you see is roughly what a crawler without JS execution sees. Missing content here confirms a client-side rendering dependency.
If raw source is empty but URL Inspection shows the full page, Googlebot is rendering your content but with a delay. The fix is still SSR or SSG to remove that dependency.
What this means for Next.js and framework sites
Modern React frameworks like Next.js give you SSR and SSG as first-class options, but defaulting to the wrong rendering mode is easy. A Next.js page using useEffect to fetch content from an API is still effectively CSR for that content, even if the page shell is server-rendered. The data needs to come from getStaticProps (SSG) or getServerSideProps (SSR) to be present in the initial HTML.
The same principle applies to any component that conditionally renders based on client-side state. If a section of your page only appears after a useState toggle or a client-side fetch resolves, Googlebot may not index it.
The AI crawler problem
AI crawlers used by ChatGPT, Perplexity, and other large language models typically do not render JavaScript at all. They fetch the raw HTML and process whatever text they find there. If your content is client-side rendered, these crawlers see nothing, and your site won’t be cited in AI-generated answers regardless of how relevant your content is.
This raises the stakes significantly. A site with CSR problems doesn’t just rank poorly in Google: it’s invisible to the AI search layer entirely. With AI Overviews appearing for a growing share of queries, and tools like Perplexity and ChatGPT replacing traditional search for many users, appearing in those answers requires your content to be in the initial HTML.
The AI SEO problem is structural, not just a ranking factor. If a crawler can’t read your content, no amount of prompt engineering or link building fixes the visibility gap.
What to do next
If your site is client-side rendered, the highest-leverage change is moving to SSR or SSG. For new projects, choose a framework that makes this easy by default. For existing sites, start with the pages that matter most for search: landing pages, product pages, category pages, blog posts.
Once rendering is correct, the rest of JavaScript SEO is mostly standard practice: real anchor tags for navigation, no blocked JS resources, static pagination, and per-page meta tags.
If you want to track whether your changes are improving visibility in both Google and AI search engines, Fokal runs target queries on a schedule and measures citation rate over time, which makes it easier to see whether your rendering fixes are actually landing.
The full picture of platform SEO is covered in the pillar guide, with framework-specific guides for React, Next.js, and single-page applications.
Checklist
- View raw page source: key content is present without JavaScript
- No navigation links use click handlers instead of
<a href> - robots.txt does not block JS bundles or CSS files
- Pagination uses real URLs, not infinite scroll or load-more buttons
- Lazy-loaded content is not hiding indexable text
- Search Console URL Inspection confirms pages are rendering correctly
- Per-page
<title>and<meta description>are set server-side - SSR or SSG is used for content pages (not CSR)
- Structured data is present in the initial HTML response