BlogYour JavaScript Framework Is Eating Your SEO and Your AI VisibilityScan · Site Quality

Your JavaScript Framework Is Eating Your SEO and Your AI Visibility

LT
Leah Tanaka · March 2026 · 10 min read

TL;DR

Your beautifully engineered SPA may be serving an empty shell to the machines that decide whether you exist. Per Vercel's analysis of AI crawler behavior, roughly 69% of AI crawlers cannot execute JavaScript, ChatGPT and Claude fetch your JS files but never run them. Google will render, eventually, but most AI answer engines retrieve a raw HTML snapshot and read what is there. If your content only appears after hydration, those bots see <div id="root"></div> and move on. The fix is server-side rendering for business-critical content, and a scan that shows you exactly what a non-JS bot actually receives.

The page your users love and the bots can't read

A modern client-rendered application is a marvel for users: instant transitions, rich interactivity, a tidy component tree. But the initial HTML response a server sends is often nearly empty, a root div, a bundle of script tags, and not much else. The actual content materializes only after the browser downloads, parses, and executes JavaScript, then makes API calls, then renders. That sequence works because your users have a full browser. The machines that index and cite you frequently do not.

This was always a risk for SEO, but Google invested heavily in rendering and mostly closed the gap for itself. The new era reopened it. Answer engines, ChatGPT's browsing, Perplexity, Claude, are not Googlebot. Per Vercel's crawler study, ChatGPT and Claude crawlers do fetch JavaScript files (ChatGPT around 11.5% of requests, Claude around 23.8%) but do not execute them. Common Crawl's CCBot, a major training-data source, does not render at all. The headline finding: about 69% of AI crawler activity cannot run JavaScript.

Fetching is not rendering. Seeing your bundle in the access logs from GPTBot does not mean the bot understood your page. It downloaded the file and then read your HTML as-is. If that HTML is an empty shell, the bot's understanding of your page is: empty shell. Your content never entered the model's view of the web.

The asymmetry between Google and the answer engines

It is worth being precise, because "JavaScript is bad for SEO" is too blunt. The reality is a spectrum:

  • Googlebot renders, it runs a headless Chromium, executes your JS, and indexes the rendered DOM. But rendering is deferred to a second wave that can lag, and it is not free; a heavy, slow, or error-prone client render can still cause missed or delayed indexing.
  • Gemini uses Google's infrastructure, so it inherits full rendering capability, the one major AI system that sees your hydrated page.
  • ChatGPT and Claude fetch but do not execute. Per OpenAI's and Anthropic's own documentation, their browsing/retrieval uses simplified text extraction from HTML snapshots, not full DOM rendering.
  • Perplexity retrieves HTML snapshots and does not execute JavaScript.
  • CCBot / training crawlers do not render.

So a client-rendered SPA is gambling that Google's deferred render lands correctly and conceding the entire non-Gemini AI channel outright. As both SEO.ai's breakdown of AI crawler JS handling and SearchVIU's 2025 rendering analysis conclude, the only safe assumption for AI visibility is: if it is not in the server-sent HTML, it does not exist.

See what the bot sees

The diagnosis is simple and you should run it before any redesign debate. Fetch your page as a non-JS client, no rendering, and count the words and key elements. If a content-rich page comes back nearly empty, you have found your problem.

# What a non-JS AI crawler actually receives
import requests
from bs4 import BeautifulSoup

UA = 'Mozilla/5.0 (compatible; GPTBot/1.1; +https://openai.com/gptbot)'
raw = requests.get(url, headers={'User-Agent': UA}, timeout=15).text
soup = BeautifulSoup(raw, 'html.parser')

# Strip script/style; measure the real text payload
for t in soup(['script', 'style', 'noscript']):
    t.decompose()
words = len(soup.get_text(' ', strip=True).split())

print(f'Words in server-sent HTML: {words}')
print(f'<h1> present: {bool(soup.find("h1"))}')
print(f'Main content tags: {len(soup.select("article, main, [role=main]"))}')
print(f'JSON-LD blocks: {len(soup.find_all("script", {"type":"application/ld+json"}))}')

# A 2,000-word article page returning ~30 words = empty shell = invisible.
if words < 100 and soup.find('div', {'id': ['root', 'app', '__next']}):
    print('VERDICT: client-rendered shell, invisible to non-JS crawlers')

Compare that word count to what a user sees in a real browser. A large gap is the entire problem, quantified. This is the same diagnostic that explains why a well-written page can be utterly absent from AI answers despite ranking fine where Google's deferred render happens to succeed.

The fix is a rendering strategy, not a framework rewrite

You do not have to abandon React, Vue, or Svelte. You have to change where the critical HTML is produced. The options, roughly in order of robustness for bot visibility:

Server-side rendering (SSR)

The server runs the framework and sends fully-formed HTML on the first request; the client hydrates for interactivity. Next.js, Nuxt, SvelteKit, and Remix make this the default path. Bots get real content immediately; users still get the SPA experience after hydration.

Static site generation (SSG / ISR)

Pre-render pages at build time (or incrementally) into static HTML. Ideal for content that does not change per request, marketing pages, docs, blog posts, product pages. The HTML is complete and cached; nothing depends on the bot running JS.

Dynamic rendering (transitional)

Detect bots and serve them a pre-rendered HTML snapshot while users get the SPA. Google considers this a workaround rather than a recommendation, but it can be a pragmatic bridge for a legacy SPA you cannot quickly convert.

// Next.js App Router: render critical content on the server.
// This component's output is in the initial HTML, bots read it.
export default async function ProductPage({ params }) {
  // Runs on the SERVER; data is in the HTML response, not post-hydration.
  const product = await getProduct(params.sku);
  return (
    <article>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <script type="application/ld+json" dangerouslySetInnerHTML={{
        __html: JSON.stringify(productSchema(product))
      }} />
    </article>
  );
}
// Client components (<AddToCart/>, <Reviews/>) hydrate after, 
// fine, because the content that must be crawlable is already in the HTML.
Put the crawlable content on the server, the interactivity on the client. The rule is not "no JavaScript." It is: anything a bot must read to understand or cite the page, headings, body copy, product data, structured data, internal links, belongs in the server-sent HTML. Buttons, carousels, and live widgets can hydrate afterward. Draw that line and you keep the SPA experience without conceding the AI channel.

Gate it so it cannot silently regress

Rendering regressions are insidious because the page looks perfect to the team, everyone has a full browser. A refactor that moves a fetch to the client, a new "optimization" that defers content, a third-party component that renders client-only: any of these can quietly empty out your server HTML while the staging review looks great. Gate it.

# CI gate: critical pages must serve real content without JS
- name: Crawlability gate (no-JS HTML payload)
  run: |
    node scripts/check-ssr.js \
      --urls /products/sample /pricing /blog/flagship \
      --min-words 200 \
      --require "h1, article, script[type='application/ld+json']" \
      --user-agent "GPTBot"
  # Fail the build if any critical URL returns a near-empty shell
  # to a non-JS user agent.

The bottom line

Client-side rendering trades machine visibility for developer convenience, and in the AI era that trade has gotten expensive: roughly 69% of AI crawlers cannot execute JavaScript, so a page whose content appears only after hydration is invisible to most answer engines and a gamble even for Google. The fix is not abandoning your framework, it is moving the crawlable content to the server via SSR or static generation, keeping interactivity on the client, and gating the build so a refactor cannot silently empty your HTML. Run the no-JS fetch on your top pages today. If a content page comes back as a shell, you have found a leak in both your organic and your AI traffic, and it is fixable this sprint.

Find the Gaps Before They Cost You

Scan audits your site for the accessibility, performance, AEO, and security gaps that quietly drain revenue and invite lawsuits, in one pass.

Try Scan Free →
Leah Tanaka Leah Tanaka writes about AI quality engineering at alt.qa, built by TheWorkCompany.