Skip to content
ExponaBLOG
TECHNICAL SEO FOR DEVELOPERS · DAY 004 / 100

How Googlebot renders JavaScript: lessons from production

What happens to your React app after Google fetches it.

Expona Admin8 min read

You've built a slick React app with client-side routing, dynamic content loading, and interactive components. You deploy it, and Google shows up to crawl it. But here's the question that keeps many developers up at night: how Googlebot renders JavaScript and whether your carefully crafted app will actually show up in search results. The answer isn't straightforward, and it depends heavily on understanding the difference between what Google sees immediately and what it sees after rendering.

Initial HTML First — Googlebot Reads the Server Response Immediately

When Googlebot fetches your page, the very first thing it does is read the HTTP response. This is pure HTML—no rendering, no JavaScript execution, no waiting. If you're serving a typical React SPA (Single Page Application), this initial response is often nearly empty: a few meta tags, a title, maybe some CSS, and a single <div id="root"></div> that your JavaScript will populate later.

This matters enormously because Google processes what's in that initial HTML response with high priority. Links found there are added to the crawl queue faster. Text content, headings, and structured data in the initial HTML are indexed more reliably. If your page title, meta description, and primary content are client-side rendered, Google may not find them during this initial read.

Let me give you a concrete example. I once worked on an e-commerce platform where product pages were built as client-side rendered React components. The initial HTML looked like this:

html
<!DOCTYPE html>
<html>
<head>
  <title>E-commerce Site</title>
  <meta name="description" content="Browse our products">
</head>
<body>
  <div id="root"></div>
  <script src="/bundle.js"></script>
</body>
</html>

Google's crawler would fetch this, see a generic title and description, and move on. The actual product name, price, and image were all rendered by JavaScript after the page loaded. Result: product pages didn't rank for their product names. We fixed it by server-rendering the critical content or using dynamic rendering.

The lesson is simple: treat your initial HTML response as your primary opportunity to communicate with Google. Put your page title, meta description, canonical tags, key content, and structured data in that initial response. Don't rely on JavaScript to add them.

Rendering Queue — Pages Are Then Rendered With an Evergreen Chromium

After Googlebot reads your initial HTML, it doesn't immediately render every page. Instead, pages enter a rendering queue. Google uses an evergreen version of Chromium—the same engine that powers Chrome—to render pages, but this happens asynchronously and with some delay. Some pages might be rendered within seconds; others might take hours or even days.

This queuing system exists because rendering JavaScript is expensive. Each page that gets rendered consumes resources: memory, CPU, time. Google has billions of pages to crawl and limited rendering capacity, so they prioritize. High-authority pages, frequently accessed pages, and pages that haven't been crawled recently get rendered sooner. A new page on a fresh domain might sit in the queue for a while.

During rendering, JavaScript executes, the DOM builds, and your React components mount. If your page makes API calls to fetch data, those calls happen. If you're using lazy loading or code splitting, that code downloads and runs. CSS-in-JS solutions process. The end result is a fully rendered DOM that Google can now analyze.

Here's where many developers make assumptions that bite them later. You test your React app locally, it works perfectly. You check the "Fetch as Google" tool in Search Console, and it also works. You think you're safe. But there's a gap between testing and production crawling. In production, Googlebot might render your page in a different environment, with different network conditions, or after the rendering queue delay. If your API calls fail, your database is slow, or you have a JavaScript error, the rendered page might be incomplete or broken.

I debugged an issue once where a NestJS API backing a Next.js app had a timeout set too low for complex queries. The page rendered successfully in my environment, but when Googlebot rendered it during off-peak hours, the API call failed and the page rendered empty. We didn't catch it because we weren't simulating the actual rendering conditions—the queue delay, the slower network, the slower database response times.

The practical takeaway: understand that rendering is decoupled from crawling. Your page is crawled immediately, but rendered later. Make sure your pages work reliably when rendered asynchronously, potentially with slower network speeds and slightly stale data.

No User Interactions — Googlebot Does Not Click, Scroll or Hover

This is perhaps the most misunderstood aspect of how Googlebot renders JavaScript. Googlebot is not a user. It doesn't click buttons, it doesn't scroll, it doesn't hover over elements, it doesn't fill out forms or trigger modals. It receives a page, renders it once with JavaScript executed, and that's it. Static content after rendering is fair game. Content behind interactions is invisible.

If your site relies on users clicking a "Load More" button to see products, Googlebot will only see the initial products. If you hide content in tabs and the first tab is empty, Googlebot won't click the other tabs. If your navigation menu appears on hover, Googlebot never hovers and never sees those links. These patterns hurt crawlability and can cause you to miss indexing opportunities.

I've seen this pattern repeatedly in CRM platforms and SaaS dashboards I've built. A common design is to show a summary view and provide an "Expand" button for details. That works great for users—better performance, less cognitive load. But if Googlebot never clicks the button, those details never get indexed. This becomes a problem if you want those details to rank independently or appear in search results.

Consider this React component:

html
<div class="product-card">
  <h3>Premium Widget</h3>
  <p>$99</p>
  <button onClick={toggleDetails}>View Details</button>
  {showDetails && (
    <div class="details">
      <p>Dimensions: 5x5x5cm</p>
      <p>Material: Premium plastic</p>
      <p>Warranty: 2 years</p>
    </div>
  )}
</div>

Googlebot renders this component with showDetails as false by default. The details content never appears in the rendered DOM that Google sees. Googlebot only ever sees the title, price, and button. To fix this for SEO, you'd either render the details visible by default or move them out of the interaction-gated pattern.

The practical approach: assume Googlebot sees your page in its initial rendered state. If content is important for SEO, it should be visible without user interaction. Use tabs, modals, and accordions for supplementary information, not for primary content.

Server-Render What Matters — Titles, Main Content, Links and Structured Data Belong in the Initial HTML

Given the constraints above, the solution becomes clear: identify what's critical for SEO and get it into the initial HTML response. This is where server-side rendering or static generation becomes essential for JavaScript-heavy sites.

What matters? Start with the basics:

  • Page titles and meta descriptions: These should be unique, descriptive, and in the initial HTML.
  • Main content: Headlines, product descriptions, article text—the content users actually search for.
  • Links: Navigation links, internal links, links to related content. These drive crawl efficiency.
  • Structured data: JSON-LD for product information, articles, breadcrumbs, FAQ, reviews. This helps Google understand your content and can trigger rich snippets.
  • Open Graph and Twitter tags: These aid social sharing and some search features.

When I built a travel booking platform using Next.js, we generated pages with Next.js SSG (Static Site Generation) for popular destinations and dynamic SSR (Server-Side Rendering) for user-specific content. The result: destination pages had full titles, descriptions, and structured data in the initial HTML, which ranked well. User-specific recommendations were client-rendered after hydration, which was fine because they weren't primary SEO targets.

Here's a simplified example of what server-rendered HTML should look like:

html
<!DOCTYPE html>
<html>
<head>
  <title>Premium Widget - 5x5x5cm, 2-Year Warranty | Widget Store</title>
  <meta name="description" content="Buy Premium Widget online. Premium plastic construction, 5x5x5cm dimensions, 2-year warranty included. In stock, ships today.">
  <link rel="canonical" href="https://widgetstore.com/products/premium-widget">
  <script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "Product",
    "name": "Premium Widget",
    "price": "99.00",
    "priceCurrency": "USD",
    "description": "Premium plastic construction...",
    "image": "https://widgetstore.com/images/premium-widget.jpg"
  }
  </script>
</head>
<body>
  <div id="root">
    <h1>Premium Widget</h1>
    <p>$99</p>
    <p>Dimensions: 5x5x5cm</p>
    <p>Material: Premium plastic</p>
    <p>Warranty: 2 years</p>
    <button>Add to Cart</button>
  </div>
  <script src="/bundle.js"></script>
</body>
</html>

Everything critical is in the initial HTML. The JavaScript bundle can enhance interactivity, add smooth animations, handle form submissions, and fetch user-specific data—but the core content is already there for Google to find.

The trade-off is real: server-rendering adds complexity. You need to fetch data on the server, handle errors gracefully, and manage rendering performance. But for sites where search traffic matters—e-commerce, SaaS, content platforms—it's a worthwhile investment. Tools like Next.js make this straightforward with built-in SSR and SSG support.

Common Mistakes

Mistake 1: Assuming Googlebot will wait for async data. If your page makes an API call on mount and that call is slow, Googlebot might render before the data arrives. Your page appears empty. Always have fallback content or server-fetch critical data.

Mistake 2: Putting all content behind a SPA router. If your app is purely client-rendered and users navigate via JavaScript, Google sees only the initial route. Use server rendering or dynamic rendering for each route that matters for SEO.

Mistake 3: Relying on client-side redirects for canonical URLs. If you redirect via JavaScript after rendering, Google might index the wrong page. Use HTTP redirects or canonical tags in the initial HTML.

Mistake 4: Not testing rendering under realistic conditions. Test your pages with tools like Google's Mobile-Friendly Test or PageSpeed Insights, which actually render your page. Check the "Fetch and Render" feature in Search Console. Don't just test locally.

Mistake 5: Forgetting that rendering delay means pages don't rank immediately. A new page might not be indexed for days even if the HTML is perfect. Be patient and check Search Console to see if pages are being indexed after rendering.

Key Takeaways

  • Googlebot reads your initial HTML response first, before any rendering happens. This is your primary opportunity to communicate critical SEO signals.
  • Rendering with Chromium happens asynchronously in a queue, so there's always a delay between crawling and rendering. Don't assume immediate results.
  • Googlebot does not interact with your page—no clicking, scrolling, or hovering. Content behind interactions won't be indexed unless it's also in the initial HTML.
  • Server-render or statically generate page titles, meta descriptions, main content, links, and structured data. These are the SEO fundamentals.
  • Client-side rendering is fine for supplementary content, personalization, and interactivity. Use it strategically, not as your default for everything.
  • Test your pages with Google's actual tools (Search Console, Mobile-Friendly Test) and check what Googlebot actually sees. Don't rely on local testing alone.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles