Navigation menu

← All articles
  • Technical SEO
  • SEO

JavaScript SEO: why your site may be invisible

Googlebot runs JavaScript, but with real limits. Here is why a slick client-rendered React or Next.js site can show up empty to search, and how to fix it.


The site looks great. Google sees a blank page.

You built a fast, modern site. React, maybe Next.js with client-side rendering, a Single Page App that feels instant once it loads. You open it in your browser and everything is there: headings, copy, product details, the lot.

Then you check Google, and almost none of it is indexed. The title is generic. The description is wrong. Pages you care about are missing entirely.

This is the JavaScript SEO trap, and it catches a lot of technically capable teams. The problem is not that your site is bad. The problem is the gap between what your browser does with your JavaScript and what a search crawler does with it.

What Googlebot actually does with JavaScript

A browser and a crawler are not the same kind of visitor. Your browser is patient, runs every script, waits for every API call, and assembles the final page for a human who is looking right at it.

Googlebot is a fleet visiting billions of URLs. It will run your JavaScript, but on its own terms and within real constraints. Two of those constraints matter most:

  • It reads the first 2MB of an HTML response. Per Google's own Googlebot documentation, the crawler reads the first 2MB of an HTML file and drops the rest. A bloated bundle of inlined JavaScript can blow past that before your real content ever appears.
  • It is mostly mobile-first. The primary crawler is Googlebot Smartphone. It evaluates the mobile version of your page, on a simulated mobile device, from US IP addresses on Pacific Time. If your content only appears after heavy scripting that a constrained mobile render struggles with, you are betting your visibility on the slowest path.

Google does render JavaScript. But rendering is a second pass that happens after the initial crawl, when resources are available. It is not free, it is not instant, and it is not guaranteed to capture a page that only assembles itself after several round-trips to your API.

Why a client-rendered SPA can come up empty

Here is the sequence that quietly breaks things.

A pure client-rendered React app ships an almost-empty HTML shell. Something like:

<body>
  <div id="root"></div>
  <script src="/bundle.js"></script>
</body>

Everything a human sees, the headings, the copy, the links, gets injected by JavaScript after bundle.js downloads, parses, runs, fetches data, and renders. To you, that takes a second. To a crawler making a snap decision across billions of URLs, that empty shell is what it grabs first.

If rendering succeeds later, great. If your bundle is large, your data fetches are slow, your render depends on a user interaction, or a single script errors out, the crawler can be left with that bare <div id="root">. There is nothing to index because, at the moment that counted, there was nothing there.

Two failure modes show up again and again:

  • Content behind a fetch. Your product description loads from an API after the page mounts. The render pass may not wait for it, so the indexed page has the layout but none of the words that would rank.
  • Links that are not links. Navigation built on onClick handlers and router.push() with no real href gives a crawler nothing to follow. Google discovers pages through <a href="...">. A button that behaves like a link to a human is invisible as a link to a crawler.

The fix: render on the server

The reliable answer is to make sure the real content exists in the HTML before JavaScript runs. That means server-side rendering (SSR) or static site generation (SSG).

  • SSG (static generation). Pages are rendered to full HTML at build time. The crawler receives complete content on the first request, no rendering pass required. Best for content that does not change per request: marketing pages, blog posts, docs, glossaries.
  • SSR (server-side rendering). Pages are rendered to HTML on the server for each request. Same payoff for the crawler, but suited to content that is dynamic or personalised.

If you are on Next.js, you largely get this by default with the App Router and Server Components, but it is easy to opt out by accident. A "use client" directive at the top of a page, data fetched only inside a useEffect, or a component that returns null until client state hydrates can all push real content back into the JavaScript-only path. Create React App and other client-only setups give you the empty-shell problem out of the box unless you add a rendering layer.

Whichever you choose, one rule from Google holds: the crawler must be able to load the same CSS and JavaScript your users load. Do not block those files in robots.txt. If Google cannot fetch your assets, it cannot render your page, and you are back to the blank shell.

How to check what Google really sees

You do not need fancy tooling to catch most of this.

  • View source, not inspect. Right-click and choose View Page Source. That is the raw HTML the server sent, before JavaScript. If your headline and body copy are not in there, a crawler may not see them on the first pass. (The Inspect panel shows the rendered DOM, which hides the problem.)
  • Disable JavaScript and reload. In Chrome DevTools, disable JavaScript and refresh a key page. What survives is roughly the worst-case view a crawler can fall back to. If the page is blank, that is your warning.
  • Use the site: operator. Search site:yourdomain.com to see what Google has actually indexed. Spot-check individual URLs the same way. Missing pages or wrong titles point straight at a rendering gap.
  • Check titles and descriptions. If Google shows a generic title pulled from your shell rather than the per-page title your JavaScript sets, the crawler indexed before your scripts ran.

The takeaway

JavaScript is not the enemy. Plenty of fast, well-ranked sites are built with React and Next.js. The trap is assuming that because a page works in your browser, it works for a crawler. Those are two different audiences with two different levels of patience.

Get the content into the HTML before the JavaScript runs, keep your assets crawlable, and use real <a href> links for navigation. Do that and your modern stack stops working against your visibility. For the wider picture of where this fits, our SEO guide for founders covers the crawl, index, and serve pipeline this all sits inside.

// next step

See what AI actually reads on your site.

Free first audit. No credit card. Your Legibility Score in under two minutes.

Run a free audit →